[bzoj3209][花神的数论题] (数位dp+费马小定理)
Description
背景
众所周知,花神多年来凭借无边的神力狂虐各大 OJ、OI、CF、TC …… 当然也包括 CH 啦。
描述
话说花神这天又来讲课了。课后照例有超级难的神题啦…… 我等蒟蒻又遭殃了。
花神的题目是这样的
设 sum(i) 表示 i 的二进制表示中 1 的个数。给出一个正整数 N ,花神要问你
派(Sum(i)),也就是 sum(1)—sum(N) 的乘积。
Input
一个正整数 N。
Output
一个数,答案模 10000007 的值。
Sample Input
样例输入一
3
Sample Output
样例输出一
2
HINT
对于样例一,1*1*2=2;
数据范围与约定
对于 100% 的数据,N≤10^15
Solution
简单的数位dp,是论文的简化
一样,预处理出f[i][j]代表i长度的二进制数下有j个1的数的数量
求解具体数据先枚举1的数量,转移一下就行,之后用快速幂连乘
难点:费马小定理的应用,对于极大的f[i][j],由于它是指数,无法直接和md取模,
根据 a^phi(p)≡1(mod p),得出把f[i][j]和phi(10000007)=9988440取模就行了
#include<iostream> #define ur 9988440 #define md 10000007 #define LL long long LL n,ans=1LL,f[53][53],tim[53]; void init() { f[0][0]=1LL; for(int i=1; i<=50; i++) { f[i][0]=f[i-1][0]; for(int j=1; j<=i; j++) f[i][j]=(f[i-1][j-1]+f[i-1][j])%ur; } } LL Q_pow(LL x,LL p) { LL res=1LL; for(; p; p>>=1LL) { if(p&1LL) res=(res*x)%md; x=(x*x)%md; } return res; } void calc() { int cnt=0; for(int i=50; ~i; i--) { if(n&(1LL<<i)) { for(int j=cnt; j<=50; j++) tim[j]=(tim[j]+f[i][j-cnt])%ur; cnt++; } } } int main() { init(); std::ios::sync_with_stdio(false); std::cin>>n; n++; calc(); for(int i=1; i<=50; i++) ans=(ans*Q_pow(i,tim[i]))%md; std::cout<<ans<<std::endl; return 0; }