Luogu P4317 花神的数论题

也是一道不错的数位DP,考虑先转成二进制后再做

转化一下问题,考虑统计出\([1,n]\)中在二进制下有\(i\)\(1\)的方案数\(cnt_i\),那么答案显然就是\(\prod i^{cnt_i}\)

然后我们还是先预处理一个东西\(s_{i,j}\),表示在二进制下前\(i\)位中填上\(j\)\(1\)的方案数,则有转移:

\(s_{i,j}=s_{i-1,j}+s_{i-1,j-1}(i>1)\),同时有\(s_{i,0}=1\)

这转移很简单吧,就是考虑这一位填上\(0/1\)

观察一下发现其实这就是个杨辉三角,不过好像并没有什么用。

接下来枚举有\(i\)\(1\)的情况,那么从高位填到低位,对于每一位上的\(1\),我后面怎么填都是满足要求的

因此此时的\(cnt_i+=s_{l,k}\)\(l\)表示后面还有多少位(比它低的位),\(k\)表示之前(包括现在)已经出现多少个\(1\),最后直接快速幂计算一下就好了。

注意到这样只能处理小于\(n\)的数的情况(一般很多二进制下的数位DP都有这个通病),所以我们直接把\(n\)加一即可。

CODE

#include<cstdio>
using namespace std;
const long long N=65,mod=10000007;
long long n,s[N][N],ans=1LL,cnt,bit[N];
inline void resolve(long long x)
{
    while (x) bit[++cnt]=x&1,x>>=1;
}
inline long long solve(long long x)
{
    register long long i; long long tot=0;
    for (i=cnt;i>=1&&~x;--i)
    if (bit[i]) tot+=s[i-1][x--];
    return tot;
}
inline long long quick_pow(long long x,long long p)
{
    long long tot=1;
    while (p)
    {
        if (p&1) tot=tot*x%mod;
        x=x*x%mod; p>>=1;
    }
    return tot;
}
int main()
{
    register long long i,j; scanf("%lld",&n); resolve(++n);
    for (s[0][0]=1,i=1;i<=cnt;++i)
    for (j=0;j<=i;++j)
    s[i][j]=j?s[i-1][j]+s[i-1][j-1]:s[i-1][j];
    for (i=1;i<=cnt;++i)
    ans=ans*quick_pow(i,solve(i))%mod;
    return printf("%lld",ans),0;
}
posted @ 2018-08-14 22:32  空気力学の詩  阅读(159)  评论(0编辑  收藏  举报