[bzoj3209][花神的数论题] (数位dp+费马小定理)

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

Solution

简单的数位dp,是论文的简化

一样,预处理出f[i][j]代表i长度的二进制数下有j个1的数的数量

求解具体数据先枚举1的数量,转移一下就行,之后用快速幂连乘

难点:费马小定理的应用,对于极大的f[i][j],由于它是指数,无法直接和md取模,

根据 a^phi(p)≡1(mod p),得出把f[i][j]和phi(10000007)=9988440取模就行了

#include<iostream>
#define ur 9988440
#define md 10000007
#define LL long long
LL n,ans=1LL,f[53][53],tim[53];
void init() {
    f[0][0]=1LL;
    for(int i=1; i<=50; i++) {
        f[i][0]=f[i-1][0];
        for(int j=1; j<=i; j++)
            f[i][j]=(f[i-1][j-1]+f[i-1][j])%ur; } }
LL Q_pow(LL x,LL p) {
    LL res=1LL;
    for(; p; p>>=1LL) {
        if(p&1LL)
            res=(res*x)%md;
        x=(x*x)%md; }
    return res; }
void calc() {
    int cnt=0;
    for(int i=50; ~i; i--) {
        if(n&(1LL<<i)) {
            for(int j=cnt; j<=50; j++)
                tim[j]=(tim[j]+f[i][j-cnt])%ur;
            cnt++; } } }
int main() {
    init();
    std::ios::sync_with_stdio(false);
    std::cin>>n; n++; calc();
    for(int i=1; i<=50; i++)
        ans=(ans*Q_pow(i,tim[i]))%md;
    std::cout<<ans<<std::endl;
    return 0; }

 

posted @ 2017-01-13 13:36  keshuqi  阅读(189)  评论(0编辑  收藏  举报