BZOJ 3209 花神的数论题

题目如下:

题目描述

背景
众所周知,花神多年来凭借无边的神力狂虐各大 OJ、OI、CF、TC …… 当然也包括 CH 啦。
描述
话说花神这天又来讲课了。课后照例有超级难的神题啦…… 我等蒟蒻又遭殃了。
花神的题目是这样的
设 sum(i) 表示 i 的二进制表示中 1 的个数。给出一个正整数 N ,花神要问你
派(Sum(i)),也就是 sum(1)—sum(N) 的乘积。

输入

一个正整数 N。

输出

一个数,答案模 10000007 的值。

样例输入

样例输入
3

样例输出

样例输出
2

对于 100% 的数据,N≤10^15

 

看到这道题首先想到暴力,但再看数据。。。。BZOJ上的题不是几个for循环就能做出来的。我首先计算了    sum(1)~sum(100),观察了规律。发现有很多重复的数字。1~100中最大的sum只有6(即63的二进制的一的个数),这样的话,计算 π(………)就会有很多重复的数字相乘,对于很多重复数字相乘,在计算机竞赛中我们是怎么解决得呢?对,就是快速幂,这样就可以很快地计算出答案了。既然已经想到了快速幂,接下来的工作就是统计每个数字出现的次数。在这里,当然暴力也是不可取的。。。在这里我们用到了数位DP的思想。这里使用的数位DP比较简单。不妨设一个数组来表示。C[i][j]。表示 i  位长的数字,其中有 j 个一,的数字个数。这个数字是指二进制数。那么它的状态转移方程是什么呢?C[i][j]=C[i-1][j]+C[i-1][j-1].   解释其实很简单。 i  位长的数字,一定又 i-1 位的数字转移而来。而 i-1位的数字转移有两种。即在 i-1 位的数字后加0或者加1,这样就可以转移成 i 位的数字了。初始化:C[1…n][0]=1.即所有位上都填0只有一种方案,这样就可以初始化所有的情况了。

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
typedef long long int ll;
const int MAXN=60+5;
const ll mod=10000007;
ll n, Ans;
ll C[MAXN][MAXN];
int l,wei[MAXN];
void pre() {
    for (int i=0;i<=60;++i)
      C[i][0]=1;
    for(int i=1;i<=60;i++)
      for(int j=1;j<=i;++j)
        C[i][j]=C[i-1][j-1]+C[i-1][j];
}
ll Solve(int x) {
    ll sum=0;
    for (int i=l;i>=1;--i) {
      if(wei[i]==1){
        sum+=C[i-1][x];
        --x;
      }
      if(x<0) break;
    }
    return sum;
}
ll Pow(ll a, ll b){
    ll tot=1;
    a%=mod;
    while(b){
      if(b&1){
        tot*=a;
        tot%=mod;
      }
      b>>=1;a*=a;a%=mod;
    }
    return tot;
}
int main()
{
    pre();
    scanf("%lld",&n);
    ++n;//只能计算小于n的个数,所以要加1
    l=0;
    while(n){
      wei[++l]=n&1;
      n>>=1;
    }
    Ans=1ll;
    for(int i=1;i<=l;++i)
      Ans=Ans*Pow(i,Solve(i))%mod;
    printf("%lld\n",Ans);
    return 0;
}

你可能感兴趣的:(ZOJ)