【数位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;
}

猜你喜欢

转载自blog.csdn.net/g21wcr/article/details/83501304