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;
}
posted @ 2018-06-21 21:47  Monster_Qi  阅读(162)  评论(0编辑  收藏  举报