1. 程式人生 > >【數位DP】bzoj3209

【數位DP】bzoj3209

【題目描述】
話說花神這天又來講課了。課後照例有超級難的神題啦…… 我等蒟蒻又遭殃了。
花神的題目是這樣的
設 sum(i) 表示 i 的二進位制表示中 1 的個數。給出一個正整數 N ,花神要問你
派(Sum(i)),也就是 sum(1)—sum(N) 的乘積取膜10^7+7的值。

【思路】數位DP,考慮出現了一個1,兩個1,三個1....的數。先預處理一下C[i][j],表示從i位裡面選j個1出來。

C[i][j]=C[i-1][j]+C[i-1][j-1]。

然後列舉前i-1位和N相同,如果N的第i位為1,這時就把第i位填成0,後面的就可以瞎填了。如果N的第i位為0,那這一位只能填0,就是前i位和N相同了。

實現的時候記錄一下之前出現了多少個1。

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int mod=10000007;
ll n,C[65][65],ans=1,cnt[65];
int len=-1,pre;
inline ll quick_pow(ll a,ll b){ll ret=1;for(;b;b>>=1,a=a*a%mod)if(b&1)ret=ret*a%mod;return ret;}
void work(){
	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];
	}
}
signed main(){
	cin>>n,work();
	while((1ll<<(++len))<=n);
	for(int i=len-1;i>=0;--i)
    //前【len-i-1】位相同,看第【len-i】位是否為1.如果是1,那這一位填0,後面i個就可以隨便填了。
	if((n>>i)&1){
		for(int j=0;j<=i;++j)
			cnt[j+pre]+=C[i][j];
        //pre記錄前面出現多少個1。
		++pre;
	}++cnt[pre];//最後要算上N自己的貢獻
	for(int i=2;i<=len;++i)(ans*=quick_pow(i,cnt[i]))%=mod;
	cout<<ans;
	return 0;
}