bzoj3209 花神的数论题 (二进制数位dp)

二进制数位dp,就是把原本的数字转化成二进制而以,原来是10进制,现在是二进制来做,没有想像的那么难

 

不知到自己怎么相出来的。。。感觉,如果没有一个明确的思路,就算做出来了,也并不能锻炼自己的能力,因为我现在需要训练的是做题的思维方法啊!

 

sum(i) 表示 i 的二进制表示中 1 的个数。给出一个正整数 N ,求sum(1)—sum(N) 的乘积。

首先,这道题直接做,感觉无从下手,那么我就想,怎么来转换一下,求1~n中每个数的一的个数总相乘之积,首先感觉到,每个数都会有唯一对应的1的个数,且一的个数的取值只有最多60,因为n最大   10^15,  那么我就想,如果枚举1的个数k,计算有多少个数含有k个1,(因为数位dp就是来做,有多少满足的数,且不关注数的大小)这样就转化为数位dp的模型了

另外,发现含有k个1的数个数可能非常多,快速幂搞一搞啦,

不过快速幂要注意超long long 的情况!!,因为在很多题mod比较大,mod<根号2^31,平方之后就有可能超int!!!!!

 

 1 #include<iostream>
 2 #include<cstdio>
 3 #include<cstring>
 4 #include<cmath> 
 5 #include<algorithm>
 6 using namespace std;
 7 
 8 typedef long long int ll;
 9 const int MAXN=60+5;
10 const ll mod=10000007;
11 
12 ll n,Ans;
13 ll c[MAXN][MAXN];
14 int l,wei[MAXN];
15 void pre()
16 {
17     for (int i=0;i<=60;++i)
18       c[i][0]=1;
19     for(int i=1;i<=60;i++)
20       for(int j=1;j<=i;++j)
21         c[i][j]=c[i-1][j-1]+c[i-1][j];//c[i][j]表示i位,j个1。 
22     scanf("%lld",&n);
23     l=0,n+=1;//只能计算小于n的个数,所以要加1
24     while(n)
25     {
26       wei[++l]=n&1;
27       n>>=1;
28     }//将n转换为2进制。 
29 }
30 ll Solve(int x)//解决有x个1的方案数 
31 {
32     ll sum=0;
33     for (int i=l;i>=1;--i)
34     {
35       if(wei[i]==1)
36       {
37         sum+=c[i-1][x];
38         x--; 
39       }
40       if(x<0) break;
41     }
42     return sum;
43 }
44 ll Pow(ll a, ll b){
45     ll tot=1;
46     a%=mod;
47     while(b)
48     {
49       if(b&1)
50       {
51         tot=a*tot%mod;
52         tot%=mod;
53       }
54       b>>=1;a*=a;a%=mod;
55     }
56     return tot;
57 }
58 int main()
59 {
60     pre();
61     Ans=1ll;
62     for(int i=1;i<=l;++i)
63       Ans=Ans*Pow(i,Solve(i))%mod;
64     printf("%lld\n",Ans);
65     return 0;
66 }

 

posted @ 2017-08-28 16:52  Kaiser-  阅读(173)  评论(0编辑  收藏  举报