首页 > 代码库 > BZOJ3209 花神的数论题

BZOJ3209 花神的数论题

本文版权归ljh2000和博客园共有,欢迎转载,但须保留此声明,并给出原文链接,谢谢合作。

 

 

 

本文作者:ljh2000
作者博客:http://www.cnblogs.com/ljh2000-jump/
转载请注明出处,侵权必究,保留最终解释权!

 

 

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$+组合数学题。
  首先这道题的数位$DP$是按照二进制数位来的,与以往的数位$DP$有所不同。具体做法就是:先预处理出$60$以内的组合数(注意到$60$以内不会爆$long$ $long$),然后我们考虑,假设我已知了二进制位中有$x$个$1$的数有$a$个,那么有$x$个$1$的数的集合产生的贡献就应该是 $x^a$ 。所以我们可以考虑枚举有多少个$1$,然后通过组合数得到这样的数的个数。我们从高位往低位扫,当我们发现某一位是$1$时,我们分类讨论,假设这一位(不妨设为第$i$位)是$0$,那么后面无论怎么填都是合法的。而且我不妨从$1$到$i-1$枚举后面还有多少个$1$,假设后面还有$j$个$1$,设在第i位之前的高位已经有$k$个$1$了,那么满足条件的数就有$C[i-1][j]$种,贡献就是 $(j+k)^{C[i-1][j]}$。另外接着考虑这一位是$1$的情况,显然这种情况下应该留到后面去讨论,接着高位的$1$的个数$+1$,继续往下做。当然很容易发现这样做会漏掉一种情况,就是这一位为$1$,后面全是$0$的情况,并未讨论,在每一位补充进去即可。
 
 
//It is made by ljh2000
#include <iostream>
#include <cstdlib>
#include <cstring>
#include <cstdio>
#include <cmath>
#include <algorithm>
using namespace std;
typedef long long LL;
const LL MOD = 10000007;
LL n,C[120][120],ans;
int cnt,k,a[120];
inline LL fast_pow(LL x,LL y){ LL r=1; while(y>0) { if(y&1) r*=x,r%=MOD; x*=x; x%=MOD; y>>=1; } return r; } 
inline void work(){
	scanf("%lld",&n); while(n>0) { a[++cnt]=n&1; n>>=1; } C[0][0]=1; k=0; ans=1;
	for(int i=1;i<=60;i++) { C[i][0]=1; for(int j=1;j<=i;j++) C[i][j]=C[i-1][j]+C[i-1][j-1]/*指数不能直接取模*/; }
	for(int i=cnt;i>=1;i--) {
		if(a[i]) {
			ans*=(k+1); ans%=MOD;
			for(int j=1;j<i;j++) ans*=fast_pow(k+j,C[i-1][j]),ans%=MOD;
			//枚举i-1位到最低位中的1的个数j,则总共1的个数位j+k,同时出现次数就为C[i-1][j]
			k++;//多了一个1
		}
	}
	printf("%lld",ans);
}

int main()
{
    work();
    return 0;
}

  

BZOJ3209 花神的数论题