数位dp习题整理——codeforces

数位dp习题整理——codeforces
1.1800
题意: 给出一个二进制数n,每次操作可以将这个数变为其二进制数位上所有1的和(3->2 ; 7->3),现在给出了一个数k,问不大于n的数中有几个数经过k次操作可以变成1。
思路: 从题中我们可以看到,一次操作可以将一个数变化为其二进制 1 的个数,于是我们设 f[i] 表示二进制中有 i 个 1 的数需要几次变化才能到达 1 。
显然 f[i] = f[__builtin_popcount(i)] + 1 ,其中 __builtin_popcount(i) 可以得出 i 二进制 1 的个数。
对于 f[i] == k-1 的 i ,接下来的工作便是找出小于等于给定数且二进制 1 的个数为 i 的数的数量,直接用数位DP。
dp[i][j]表示枚举到第i位,有j个1的方案数。

#include<bits/stdc++.h>
#include<tr1/unordered_map>
using namespace std;
typedef long long ll;
const int maxn=1e3+5;
const int mod=1e9+7;
const int INF=1e9+7;
int f[maxn],dp[maxn][maxn],k;
char s[maxn];
void init()
{
    
    
    for(int i=2;i<=1000;i++)
    {
    
    
        f[i]=f[__builtin_popcount(i)]+1;
    }
}
int dfs(int now,int num1,int limit)
{
    
    
    if(!now)
        return f[num1]==k-1;
    if(!limit&&~dp[now][num1])
        return dp[now][num1];
    int up=limit?s[now]-'0':1,ans=0;
    for(int i=0;i<=up;i++)
    {
    
    
        ans+=dfs(now-1,num1+(i==1),limit&&i==up);
        ans%=mod;
    }
    if(!limit)
        dp[now][num1]=ans;
    return ans;
}
int main()
{
    
    
    init();
    scanf("%s%d",s+1,&k);
    int len=strlen(s+1);
    for(int i=1;i<=len/2;i++)
        swap(s[i],s[len-i+1]);
    //cout<<(s+1)<<endl;
    memset(dp,-1,sizeof dp);
    ll ans=dfs(len,0,1);
    if(k==0)
        ans=1;
    if(k==1)
        ans=len-1;
    cout<<ans<<endl;
}

2.2200
题意: 现在定义d-magic数字,就是一个没有前导0的数,s[i]=d,s[j]!=d,i是奇数,j是偶数。
然后现在给你m,d,a,b。问你在[a,b]内,是m的倍数,且是d-magic的数字有多少个。
思路: 奇偶填数的问题直接填d和非d就行,m的倍数直接拆位的过程%m就行。dp[i][j]代表枚举到第i位当前为的余数是j的方案数。
坑点: a-1比较难算,特判a就行

#include<bits/stdc++.h>
#include<tr1/unordered_map>
using namespace std;
typedef long long ll;
const int maxn=2005;
const int mod=1e9+7;
const int INF=1e9+7;
int dp[maxn][maxn],m,d,len;
char a[maxn],b[maxn];
int dfs(int now,int res,int limit,char s[])
{
    
    
    if(now==len+1)
        return res==0;
    if(!limit&&~dp[now][res])
        return dp[now][res];
    int up=limit?(s[now]-'0'):9,ans=0;
    if(now%2==0)
    {
    
    
        for(int i=0; i<=up; i++)
        {
    
    
            if(i!=d)
                continue;
            ans=ans+dfs(now+1,(res*10+i)%m,i==up&&limit,s);
            ans%=mod;
        }
    }
    else
    {
    
    
        for(int i=0; i<=up; i++)
        {
    
    
            if(i==d)
                continue;
            ans=ans+dfs(now+1,(res*10+i)%m,i==up&&limit,s);
            ans%=mod;
        }
    }
    if(!limit)
        return dp[now][res]=ans;
    return ans;
}
bool judge(char a[])
{
    
    
    int sum=0;
    for(int i=1; i<=len; i++)
    {
    
    
        sum=sum*10+(a[i]-'0');
        sum%=m;
        if(i%2==0)
        {
    
    
            if(a[i]-'0'!=d)
                return 0;
        }
        else
        {
    
    
            if(a[i]-'0'==d)
                return 0;
        }
    }
    return sum%m==0;
}
int solve(char a[])
{
    
    
    memset(dp,-1,sizeof dp);
    return  dfs(1,0,1,a);
}
int main()
{
    
    
    scanf("%d%d",&m,&d);
    scanf("%s%s",a+1,b+1);
    len=strlen(a+1);
    int ans=(solve(b)-solve(a)+mod)%mod;
    if(judge(a))
        ans=(ans+1)%mod;
    cout<<ans<<endl;

}

3.1800
题意:给你l,r,让你找在这个闭区间内位数不为0不超过3的个数,1<=l,r<=1e18
思路: 统计位数值是否==0,dp[i][j]表示枚举到第i位有j个非零的方案数。

#include<bits/stdc++.h>
#include<tr1/unordered_map>
using namespace std;
typedef long long ll;
const int maxn=2005;
const int mod=1e9+7;
const int INF=1e9+7;
int dp[20][20],m,d,len,a[20];
ll dfs(ll now,ll num,ll limit)
{
    
    
    if(!now)
        return num<=3;
    if(!limit&&~dp[now][num])
        return dp[now][num];
    int up=limit?a[now]:9;
    ll ans=0;
    for(int i=0;i<=up;i++)
    {
    
    
        if(num>3&&i!=0)
            continue;
        ans+=dfs(now-1,num+(i!=0),limit&&i==up);
    }
    if(!limit)
        return dp[now][num]=ans;
    return ans;
}
ll solve(ll num)
{
    
    
    a[0]=0;
    while(num)
    {
    
    
        a[++a[0]]=num%10;
        num/=10;
    }
    memset(dp,-1,sizeof dp);
    return dfs(a[0],0,1);

}
int main()
{
    
    
    int t;
    cin>>t;
    while(t--)
    {
    
    
        ll l,r;
        scanf("%lld%lld",&l,&r);
        printf("%lld\n",solve(r)-solve(l-1));
    }
}

4.2000+
题意:给你一个N,然后问你[1,n]内,有多少个数可以被由它转化成的二进制里面的1的个数整除(读起来比较迷,实际上还是比较好理解的
比如9的二进制形式为1001,但是9%2!=0,所以它不是
比如8的二进制形式为1000,8%1==0,所以它是
思路:我们可以统计1的数量,dp[now][num1][sum]?可是数组不可能开下,发现最多有65个1,直接枚举1的数量不就行了,dp[now][res][num1]表示枚举到第i位余数是res当前有num个1的方案数。
坑点: 要用ull,输入输出lld,

#include<bits/stdc++.h>
#include<tr1/unordered_map>
using namespace std;
typedef unsigned long long ll;
const int maxn=65;
const ll mod=1e9+7;
ll dp[maxn][maxn][maxn],a[maxn],target;
ll poww[maxn];
ll dfs(ll now,ll res,ll num0,ll limit)
{
    
    
    if(!now)
        return (res==0&&num0==target);
    if(!limit&&~dp[now][res][num0])
        return dp[now][res][num0];
    ll up=limit?a[now]:1,ans=0;
    for(ll i=0; i<=up; i++)
    {
    
    
        if(i==1)
            ans=ans+dfs(now-1,(res+poww[now])%target,num0+1,i==up&&limit);
        else
            ans=ans+dfs(now-1,res,num0,i==up&&limit);
    }
    if(!limit)
        return dp[now][res][num0]=ans;
    return ans;
}
ll solve(ll num)
{
    
    
    a[0]=0;
    while(num)
    {
    
    
        a[++a[0]]=num%2;
        num/=2;
    }
    ll ans=0;
    for(ll i=1; i<=a[0]; i++)
    {
    
    
        target=i;
        memset(dp,-1,sizeof dp);
        ans=ans+dfs(a[0],0,0,1);
        //cout<<ans<<" ";
    }
    return ans;
}
int main()
{
    
    
    ll n;
    scanf("%llu",&n);
    poww[1]=1;
    for(ll i=2; i<=64; i++)
        poww[i]=poww[i-1]*2;
    //cout<<poww[65]<<endl;
    //cout<<(unsigned long long)1e19<<endl;
    printf("%llu\n",solve(n));
}

5.2000+
题意: 求1到n的所有整数中,各位数字之和能整除它本身的数的个数
思路: 枚举数字和,存余数,dp[i][j][k]表示枚举到第i位余数是j,数字和是sum的方案数

#include<bits/stdc++.h>
#include<tr1/unordered_map>
using namespace std;
typedef long long ll;
const int maxn=110;
const ll mod=1e9+7;
ll dp[maxn][maxn][maxn],a[maxn],target;
ll poww[maxn];
ll dfs(ll now,ll res,ll sum,ll limit)
{
    
    
    if(!now)
        return (res==0&&sum==target);
    if(!limit&&~dp[now][res][sum])
        return dp[now][res][sum];
    ll up=limit?a[now]:9,ans=0;
    for(ll i=0; i<=up; i++)
    {
    
    
        ans=ans+dfs(now-1,(res%target+i*poww[now]%target)%target,sum+i,i==up&&limit);
    }
    if(!limit)
        return dp[now][res][sum]=ans;
    return ans;
}
ll solve(ll num)
{
    
    
    a[0]=0;
    while(num)
    {
    
    
        a[++a[0]]=num%10;
        num/=10;
    }
    ll ans=0;
    for(ll i=1; i<=108; i++)
    {
    
    
        target=i;
        memset(dp,-1,sizeof dp);
        ans=ans+dfs(a[0],0,0,1);
    }
    return ans;
}
int main()
{
    
    
    freopen("just.in","r",stdin);
    freopen("just.out","w",stdout);
    ll n;
    poww[1]=1;
    for(int i=2;i<=18;i++)
        poww[i]=poww[i-1]*10;
    scanf("%lld",&n);
    printf("%lld\n",solve(n));
}

6.2200
题意:一个数是魔幻数,当且仅当这个数中0~base-1(假设它是base进制)的个数都为偶数,假设1010(二进制数)就是一个魔幻数,问你十进制的l到r中在b进制下,有多少个魔幻数。
思路:base<=10,直接记录每个数的个数?dp[now][base][num0][num1][num2][num3][num4][num5][num6][num7][num8][num9],复杂度2^10 * 70 * 12可以哦,1356ms,不过姿势太丑了,写太多了,能过就行
还有什么其他的方法?
偶数,每一位,可以异或啊,状态压缩成2048,复杂度2048 * 70 * 12,873ms
为什么下面写法快一点?写得好看?姿势对?
应该是数组维数的问题…

#include<bits/stdc++.h>
#include<tr1/unordered_map>
using namespace std;
typedef long long ll;
const int maxn=110;
const ll mod=1e9+7;
ll dp[70][12][2][2][2][2][2][2][2][2][2][2],a[maxn],base;
ll dfs(ll now,ll base,ll num0,ll num1,ll num2,ll num3,ll num4,ll num5,ll num6,ll num7,ll num8,ll num9,ll lead,ll limit)
{
    
    
    if(!now)
    {
    
    
        return !num0&&!num1&&!num2&&!num3&&!num4&&!num5&&!num6&&!num7&&!num8&&!num9;
    }
    if(!lead&&!limit&&~dp[now][base][num0][num1][num2][num3][num4][num5][num6][num7][num8][num9])
        return dp[now][base][num0][num1][num2][num3][num4][num5][num6][num7][num8][num9];
    ll up=limit?a[now]:base-1,ans=0;
    for(ll i=0; i<=up; i++)
    {
    
    
        if(i==0&&lead)
            ans=ans+dfs(now-1,base,num0,num1,num2,num3,num4,num5,num6,num7,num8,num9,1,limit&&i==up);
        else
        {
    
    
            if(i==0) ans=ans+dfs(now-1,base,num0^1,num1,num2,num3,num4,num5,num6,num7,num8,num9,0,limit&&i==up);
            else if(i==1)ans=ans+dfs(now-1,base,num0,num1^1,num2,num3,num4,num5,num6,num7,num8,num9,0,limit&&i==up);
            else if(i==2)ans=ans+dfs(now-1,base,num0,num1,num2^1,num3,num4,num5,num6,num7,num8,num9,0,limit&&i==up);
            else if(i==3)ans=ans+dfs(now-1,base,num0,num1,num2,num3^1,num4,num5,num6,num7,num8,num9,0,limit&&i==up);
            else if(i==4)ans=ans+dfs(now-1,base,num0,num1,num2,num3,num4^1,num5,num6,num7,num8,num9,0,limit&&i==up);
            else if(i==5)ans=ans+dfs(now-1,base,num0,num1,num2,num3,num4,num5^1,num6,num7,num8,num9,0,limit&&i==up);
            else if(i==6)ans=ans+dfs(now-1,base,num0,num1,num2,num3,num4,num5,num6^1,num7,num8,num9,0,limit&&i==up);
            else if(i==7)ans=ans+dfs(now-1,base,num0,num1,num2,num3,num4,num5,num6,num7^1,num8,num9,0,limit&&i==up);
            else if(i==8)ans=ans+dfs(now-1,base,num0,num1,num2,num3,num4,num5,num6,num7,num8^1,num9,0,limit&&i==up);
            else if(i==9)ans=ans+dfs(now-1,base,num0,num1,num2,num3,num4,num5,num6,num7,num8,num9^1,0,limit&&i==up);

        }

    }
    if(!lead&&!limit)
        return dp[now][base][num0][num1][num2][num3][num4][num5][num6][num7][num8][num9]=ans;
    return ans;
}
ll solve(ll num)
{
    
    
    a[0]=0;
    while(num)
    {
    
    
        a[++a[0]]=num%base;
        num/=base;
    }
    return dfs(a[0],base,0,0,0,0,0,0,0,0,0,0,1,1);
}
int main()
{
    
    
    int t;
    scanf("%d",&t);
    memset(dp,-1,sizeof dp);
    while(t--)
    {
    
    
        ll l,r;
        scanf("%lld%lld%lld",&base,&l,&r);
        printf("%lld\n",solve(r)-solve(l-1));
    }
}
#include<bits/stdc++.h>
#include<tr1/unordered_map>
using namespace std;
typedef long long ll;
const int maxn=110;
const ll mod=1e9+7;
ll dp[70][12][3005],a[maxn],base;
ll dfs(ll now,ll base,ll num,ll lead,ll limit)
{
    
    
    if(!now)
    {
    
    
        return !num;
    }
    if(!lead&&!limit&&~dp[now][base][num])
        return dp[now][base][num];
    ll up=limit?a[now]:base-1,ans=0;
    for(ll i=0; i<=up; i++)
    {
    
    
        if(i==0&&lead)
            ans=ans+dfs(now-1,base,num,1,i==up&&limit);
        else
        {
    
    
           ans=ans+dfs(now-1,base,num^(1<<i),0,i==up&&limit);
        }

    }
    if(!lead&&!limit)
        return dp[now][base][num]=ans;
    return ans;
}
ll solve(ll num)
{
    
    
    a[0]=0;
    while(num)
    {
    
    
        a[++a[0]]=num%base;
        num/=base;
    }
    return dfs(a[0],base,0,1,1);
}
int main()
{
    
    
    int t;
    scanf("%d",&t);
    memset(dp,-1,sizeof dp);
    while(t--)
    {
    
    
        ll l,r;
        scanf("%lld%lld%lld",&base,&l,&r);
        printf("%lld\n",solve(r)-solve(l-1));
    }
}

7.2400
题意:这个数能整除它的所有位上非零整数。问[l,r]之间的Beautiful Numbers的个数。
思路:若一个数能整除它的所有的非零数位,那么相当于它能整除个位数的最小公倍数。因此记忆化搜索中的参数除了len(当前位)和up(是否达到上界),有一个prelcm表示前面的数的最小公倍数,判断这个数是否是Beautiful Numbers,还要有一个参数表示前面数,但是这个数太大,需要缩小它的范围。
难点:
缩小前面组成的数的范围。
可以发现所有个位数的最小公倍数是2520,假设当前的Beautiful Numbers是x,
那么 x % lcm{dig[i]} = 0,
又 2520%lcm{dig[i]} = 0,
那么x%2520%lcm{ dig[i] } = 0,x范围由9*10^18变为2520。
处理超内存问题。
经过分析后可以设出dp[20][2050][2050],dp[i][j][k]表示处理到i位,前面的数的最小公倍数为j,前面的数%2520为k。但这样
明显会TLE。。因为1~9组成的最小公倍数只有48个,可以离散化,这样数组就降到了dp[20][50][2520]。

#include<bits/stdc++.h>
#include<tr1/unordered_map>
using namespace std;
typedef long long ll;
const int maxn=1e6+5;
const int mod=1e9+7;
ll dp[20][50][2520],a[maxn],mp[maxn];
ll __lcm(ll a,ll b)
{
    
    
    return a/__gcd(a,b)*b;
}
ll dfs(ll now,ll lcm,ll res,ll limit)
{
    
    
    if(!now)
    {
    
    
        return res%lcm==0;
    }
    if(!limit&&~dp[now][mp[lcm]][res])
        return dp[now][mp[lcm]][res];
    ll up=limit?a[now]:9,ans=0;
    for(ll i=0; i<=up; i++)
    {
    
    
        ans=ans+dfs(now-1,i==0?lcm:__lcm(lcm,i),(res*10+i)%2520,limit&&i==up);
    }
    if(!limit)
        return dp[now][mp[lcm]][res]=ans;
    return ans;
}
ll solve(ll num)
{
    
    
    //if(num==0)return 0;
    a[0]=0;
    while(num)
    {
    
    
        a[++a[0]]=num%10;
        num/=10;
    }
    return dfs(a[0],1,0,1);
}
int main()
{
    
    
    int t,cnt=0;
    scanf("%d",&t);
    memset(dp,-1,sizeof dp);
    for(int i=1; i<=2520; i++)
    {
    
    
        if(2520%i==0)
            mp[i]=++cnt;
    }
    memset(dp,-1,sizeof dp);
    while(t--)
    {
    
    
        ll l,r;
        scanf("%lld%lld",&l,&r);
        printf("%lld\n",solve(r)-solve(l-1));
    }
}

猜你喜欢

转载自blog.csdn.net/qq_43653111/article/details/104500036
今日推荐