【数位dp】bzoj3209: 花神的数论题
Description
背景
众所周知,花神多年来凭借无边的神力狂虐各大 OJ、OI、CF、TC …… 当然也包括 CH 啦。
描述
话说花神这天又来讲课了。课后照例有超级难的神题啦…… 我等蒟蒻又遭殃了。
花神的题目是这样的
设 sum(i) 表示 i 的二进制表示中 1 的个数。给出一个正整数 N ,花神要问你
派(Sum(i)),也就是 sum(1)—sum(N) 的乘积。
Input
一个正整数 N。
Output
一个数,答案模 10000007 的值。
Sample Input
样例输入一
3
3
Sample Output
样例输出一
2
2
HINT
对于样例一,1*1*2=2;
数据范围与约定
对于 100% 的数据,N≤10^15
题目分析
是一道入门的数位dp(组合数)题。
但是这题结合了很多出题人的恶意,并且具有一定的启示作用。
1 #include<bits/stdc++.h> 2 const int MO = 10000007; 3 4 int ans,pre; 5 int f[103][103]; 6 int digit[23]; 7 long long n; 8 9 int qmi(int a, int b) 10 { 11 int ret = 1; 12 while (b) 13 { 14 if (b&1) ret = 1ll*ret*a%MO; 15 a = 1ll*a*a%MO; 16 b >>= 1; 17 } 18 return ret; 19 } 20 int main() 21 { 22 scanf("%lld",&n); 23 f[0][0] = 1, ans = 1; 24 for (n++; n; n>>=1) digit[++digit[0]] = n&1; 25 for (int i=1; i<=digit[0]; i++) 26 { 27 f[i][0] = 1; 28 for (int j=1; j<=i; j++) 29 f[i][j] = f[i-1][j]+f[i-1][j-1]; //预处理组合数 30 } 31 for (int i=digit[0]; i; i--) 32 if (digit[i]){ 33 for (int j=i-1; j>=1; j--) 34 ans = 1ll*ans*qmi(pre+j, f[i-1][j])%MO; 35 pre++; 36 } 37 printf("%d\n",ans); 38 return 0; 39 }
最初会自然地想到上面这种dp方法。
但是!这里的细节显然崩坏了。
二进制拆分
1 int digit[23];
二进制拆分时候$digit[]$干嘛开这么小啊……注意要大概开个三倍。
组合数取模
1 for (int i=1; i<=digit[0]; i++) 2 { 3 f[i][0] = 1; 4 for (int j=1; j<=i; j++) 5 f[i][j] = f[i-1][j]+f[i-1][j-1]; 6 }
这里组合数不取模显然是会溢出的。但是,重点是取什么模呢?可能会不假思索地%1e7+7,然而实际上1e7+7并不是一个素数,所以这里要回到欧拉定理,我们有$φ(1e7+7)=9988440$。再者就是注意这里只有组合数需要对$φ(1e7+7)$取模。
溢出会RE;或是莫名其妙TLE。
dp的转移
1 for (int i=digit[0]; i; i--) 2 if (digit[i]){ 3 for (int j=i-1; j>=1; j--) 4 ans = 1ll*ans*qmi(pre+j, f[i-1][j])%MO; 5 pre++; 6 }
注意到这里内层的$j>=1$,但是由于后面的元素可以不选,事实上$j$应该$>=0$才对。
手调时候会发现当$j=0,pre=0$时,$ans$就等于0了。
所以还要在快速幂里特判一层: if (!a) return 1; 。
正确代码
1 #include<bits/stdc++.h> 2 const int MO = 10000007; 3 4 int ans,pre; 5 int f[103][103]; 6 int digit[53]; 7 long long n; 8 9 int qmi(int a, int b) 10 { 11 if (!a) return 1; 12 int ret = 1; 13 while (b) 14 { 15 if (b&1) ret = 1ll*ret*a%MO; 16 a = 1ll*a*a%MO; 17 b >>= 1; 18 } 19 return ret; 20 } 21 int main() 22 { 23 scanf("%lld",&n); 24 f[0][0] = 1, ans = 1; 25 for (n++; n; n>>=1) digit[++digit[0]] = n&1; 26 for (int i=1; i<=digit[0]; i++) 27 { 28 f[i][0] = 1; 29 for (int j=1; j<=i; j++) 30 f[i][j] = (f[i-1][j]+f[i-1][j-1])%9988440; 31 } 32 for (int i=digit[0]; i; i--) 33 if (digit[i]){ 34 for (int j=i-1; j>=0; j--) 35 ans = 1ll*ans*qmi(pre+j, f[i-1][j])%MO; 36 pre++; 37 } 38 printf("%d\n",ans); 39 return 0; 40 }
END