BZOJ 3209 花神的数论题
题目如下:
题目描述
背景
众所周知,花神多年来凭借无边的神力狂虐各大 OJ、OI、CF、TC …… 当然也包括 CH 啦。
描述
话说花神这天又来讲课了。课后照例有超级难的神题啦…… 我等蒟蒻又遭殃了。
花神的题目是这样的
设 sum(i) 表示 i 的二进制表示中 1 的个数。给出一个正整数 N ,花神要问你
派(Sum(i)),也就是 sum(1)—sum(N) 的乘积。
输入
一个正整数 N。
输出
一个数,答案模 10000007 的值。
样例输入
样例输入
3
样例输出
样例输出
2
对于 100% 的数据,N≤10^15
看到这道题首先想到暴力,但再看数据。。。。BZOJ上的题不是几个for循环就能做出来的。我首先计算了 sum(1)~sum(100),观察了规律。发现有很多重复的数字。1~100中最大的sum只有6(即63的二进制的一的个数),这样的话,计算 π(………)就会有很多重复的数字相乘,对于很多重复数字相乘,在计算机竞赛中我们是怎么解决得呢?对,就是快速幂,这样就可以很快地计算出答案了。既然已经想到了快速幂,接下来的工作就是统计每个数字出现的次数。在这里,当然暴力也是不可取的。。。在这里我们用到了数位DP的思想。这里使用的数位DP比较简单。不妨设一个数组来表示。C[i][j]。表示 i 位长的数字,其中有 j 个一,的数字个数。这个数字是指二进制数。那么它的状态转移方程是什么呢?C[i][j]=C[i-1][j]+C[i-1][j-1]. 解释其实很简单。 i 位长的数字,一定又 i-1 位的数字转移而来。而 i-1位的数字转移有两种。即在 i-1 位的数字后加0或者加1,这样就可以转移成 i 位的数字了。初始化:C[1…n][0]=1.即所有位上都填0只有一种方案,这样就可以初始化所有的情况了。
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> using namespace std; typedef long long int ll; const int MAXN=60+5; const ll mod=10000007; ll n, Ans; ll C[MAXN][MAXN]; int l,wei[MAXN]; void pre() { for (int i=0;i<=60;++i) C[i][0]=1; for(int i=1;i<=60;i++) for(int j=1;j<=i;++j) C[i][j]=C[i-1][j-1]+C[i-1][j]; } ll Solve(int x) { ll sum=0; for (int i=l;i>=1;--i) { if(wei[i]==1){ sum+=C[i-1][x]; --x; } if(x<0) break; } return sum; } ll Pow(ll a, ll b){ ll tot=1; a%=mod; while(b){ if(b&1){ tot*=a; tot%=mod; } b>>=1;a*=a;a%=mod; } return tot; } int main() { pre(); scanf("%lld",&n); ++n;//只能计算小于n的个数,所以要加1 l=0; while(n){ wei[++l]=n&1; n>>=1; } Ans=1ll; for(int i=1;i<=l;++i) Ans=Ans*Pow(i,Solve(i))%mod; printf("%lld\n",Ans); return 0; }