3209: 花神的数论题
Time Limit: 10 Sec Memory Limit: 128 MB
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
Source
原创 Memphis
一开始想的是能不能用数位DP做:用f(i,j)表示二进制位数共i位且一共有j个1的数的个数——当然是可以的,然而细细分析之后发现有更加简便的做法。我们考虑数n的二进制,假设共l位,分别分别为b0 b1 b2…bl-1。为了计算出最终答案我们只需要统计出二进制中1的个数分别为1,2,…,l的数的个数。我们从高位往地位进行计算,若n的bi=1,那么对于那些bi=0的数,我们可以直接将他们对答案的贡献计算出来,即:设i之前有m个1,i之后还有k位,那么对于任意0<=j<=k,我们便找到了C(k,j)(这是组合数= =)个总共有m+j个1的数。最终不要忘记了算上n本身也要计入答案。统计出所有的个数后,使用快速幂求解即可。注意:10000007不是质数,此题题不能也没必要使用费马小定理……
#include<iostream>
#define bit(a) (1ll<<a)
using namespace std;
const int p=10000007;
typedef unsigned long long LL;
LL c[60][60],cnt[60];
void add(int l,int pre){for(int i=0;i<=l;i++)cnt[i+pre]+=c[l][i];}
LL fpow(LL a,LL b){
if(b==0)return 1;
if(b==1)return a;
LL t=fpow(a,b/2);
t=t*t%p;
return b&1?t*a%p:t;
}
int main(){
for(int i=0;i<60;i++){
c[i][0]=1;
for(int j=1;j<=i;j++)c[i][j]=c[i-1][j-1]+c[i-1][j];
}
LL n;
cin>>n;
int l=0;
while(bit(l)<=n)l++;
int pre=0;
for(int i=l-1;~i;i--)if(bit(i)&n)add(i,pre++);
cnt[pre]++;
LL ans=1;
for(int i=1;i<=l;i++)ans=ans*fpow(i,cnt[i])%p;
cout<<ans<<endl;
return 0;
}