【数位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

Sample Output

样例输出一

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

posted @ 2018-07-20 19:47  AntiQuality  阅读(219)  评论(0编辑  收藏  举报