洛谷4317花神的数论题(数位DP)

传送门

本来是想刷数论题的,万万没想到的是,这道题题目是叫数论题,但其实它是道数位DP呢。

既然sum(i) 表示 的二进制表示中1的个数,而数据范围又很大,是1e15,暴力肯定是不行的。

但我们知道,肯定有很多数二进制的1的个数是一样的,考虑可不可以把问题转化成对于每一个k,找出二进制里有k个1的数有多少个。

我们发现把N换为2进制的话,也不过就是50位的样子。那么最多也才50个1,那么k从 1 for到 50,每个k找一遍,把个数存一下。

问题转化为:对于一个区间的数,找出二进制中有k个1的数有多少个----->数位dp。

#include<bits/stdc++.h>
#define LL long long
#define mod 10000007
using namespace std;
LL read()
{
    LL x=0,f=1;char s=getchar();
    while(s<'0'||s>'9'){if(s=='-')f=-1;s=getchar();}
    while(s>='0'&&s<='9'){x=x*10+s-'0';s=getchar();}
    return x*f;
}
int num[52];
LL dp[52][52][52][3],f[52];//小心爆int 
int tot=0;
LL dfs(int pos,int cnt,int goal,int lim)
{
    if(!pos) return cnt==goal;
    if(~dp[pos][cnt][goal][lim])return dp[pos][cnt][goal][lim];
    int maxx=lim?num[pos]:1;
    LL res=0;
    for(int i=0;i<=maxx;++i)
      res+=dfs(pos-1,cnt+(i==1),goal,lim&&(i==maxx));
    return dp[pos][cnt][goal][lim]=res;
}
LL quick(LL a,LL x)
{
    LL ans=1;
    while(x)
    {
        if(x&1)ans=ans*a%mod;
        a=a*a%mod;x>>=1;
    }
    return ans;
}
void solve(LL x)
{
    while(x)
    {
        num[++tot]=x&1;
        x>>=1;
    }
    for(int i=2;i<=50;++i)memset(dp,-1,sizeof(dp)),f[i]=dfs(tot,0,i,1);//记得清空,每次要求的1的个数不同 
    LL ans=1;
    for(int i=2;i<=50;++i)
      if(f[i])ans=ans*quick(i,f[i])%mod;
    printf("%lld\n",ans);
}
int main()
{
    LL n=read();
    solve(n);
} 
View Code

猜你喜欢

转载自www.cnblogs.com/yyys-/p/11295638.html
今日推荐