来自FallDream的博客,未经允许,请勿转载,谢谢,
背景
众所周知,花神多年来凭借无边的神力狂虐各大 OJ、OI、CF、TC …… 当然也包括 CH 啦。描述话说花神这天又来讲课了。课后照例有超级难的神题啦…… 我等蒟蒻又遭殃了。花神的题目是这样的设 sum(i) 表示 i 的二进制表示中 1 的个数。给出一个正整数 N ,花神要问你派(Sum(i)),也就是 sum(1)—sum(N) 的乘积取膜10^7+7的值。n<=10^15
感觉别人的题解写的非常有道理 可以数位dp+快速幂 讲讲我的乱搞吧
二进制位数不同的分开看 发现是
1
12
1223
....
每一个长度的所有数字的sum其实就是前一个搬过来,再接上一个加上一的。
然后就可以乱搞了呗 预处理每个长度之后,先1248这样的加上去,最后剩下一个不完整的段,可以根据这个规律表示成少一位的一个不完整的段加上最多一个完整的段,递归的时候记一下后移了多少位即可。
复杂度是logn^2+logn
然后这个膜数不是质数 真的坑
#include#include #define mod 10000007#define MN 50#define ll long longusing namespace std;ll Ans[MN+5],s[MN+5][MN+5];ll n;void Add(int i,int Move=0){ for(int j=1;j+Move<=MN;++j) Ans[j+Move]+=s[i][j];}void Calc(int i,ll n,int ad){ if(!n) return; if(n<(1LL<<(i-1))) Calc(i-1,n,ad); else Calc(i-1,n-(1LL<<(i-1)),ad+1),Add(i,ad);}inline int pow(int x,ll k){ int sum=1; for(;k;k>>=1,x=1LL*x*x%mod) if(k&1) sum=1LL*sum*x%mod; return sum;}int main(){ s[1][1]=1; for(int i=2;i<=MN;++i) for(int j=1;j<=MN;++j) s[i][j]=s[i-1][j]+s[i-1][j-1]; scanf("%lld",&n);if(!n) return 0*puts("0"); for(int i=1;(1LL<<(i-1))<=n;n-=(1LL<<(i-1)),++i) Add(i); Calc(MN,n,0);int ans=1; for(int i=1;i<=MN;++i) ans=1LL*ans*pow(i,Ans[i])%mod; printf("%d\n",ans); return 0;}