BZOJ 3209: 花神的数论题
Time Limit: 10 Sec Memory Limit: 128 MB
Submit: 2740 Solved: 1240
[Submit][Status][Discuss]
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,dp[i][j]表示二进制意义下的第i位数有j个1,我们枚举所有的j,之后使用快速幂。
转移方程:dp[i][j]=dp[i-1][j-1]+dp[i-1][j],表示这位数可以是0或1,处置为dp[i][0]=1,表示全部
填0的方案是1。观察这个方程,突然发现这不就是组合数的递推式??继续思考他的意义,我
认为组合数的意义是所有的1里挑出k个0,就比如说1111这个二进制数,它的答案其实就是
(4^C(4,0)) * (3^C(4,1)) * (2^(C(4,2)) * (1^(C(4,3)) 是因为我们考虑这些1中取k个0,其余的1
对答案的贡献就是(n-k)^C(n,k)。。个人想法,欢迎大神来拍砖。
代码
#include<bits/stdc++.h>
#define LL long long
using namespace std;
const int N = 105;
const int mod = 10000007;
LL n,ans=1;
int cnt,a[N];
LL c[N][N];
inline void C(){
c[1][1]=c[1][0]=1;c[0][0]=1;
for(register int i=2;i<=60;i++){
c[i][0]=1;
for(register int j=1;j<=i;j++)
c[i][j]=c[i-1][j]+c[i-1][j-1];
}
}
inline LL solve(int x){
LL sum=0;
for(register int i=cnt;i;i--){
if(a[i]) sum+=c[i-1][x--];
if(x<0) break;
}
return sum;
}
inline LL fast(LL a,LL b){
LL ret=1;
for(;b;b>>=1){
if(b&1) ret=ret*a%mod;
a=a*a%mod;
}
return ret;
}
signed main(){
C();
scanf("%lld",&n);n++;
for(;n;n>>=1) a[++cnt]=(n&1);
// for(register int i=cnt;i;i--) cout<<a[i]<<" ";
for(register int i=1;i<=cnt;++i)
ans=(ans*fast(i,solve(i)))%mod;
printf("%lld",ans);
return 0;
}