数位dp总结

142 篇文章 1 订阅
92 篇文章 0 订阅

补题的时候有个题需要数位dp,有去重学了一波上一年就学过的数位dp,又学一遍感觉上一年学了个寂寞,,,

这种问题大多数都是和数的每一位的数字有关,一般是一个数的数位之间存在着某种关系,让求具有这种关系的数字在[a,b]的范围内有多少个。

一般代码有两种,一种是偏递推的dp,另一种是记忆化搜索,前者太难了就放弃了,,,

先按一道例题来说

P2602 [ZJOI2010] 数字计数 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

一般的记忆化搜索的框架

int dfs(int pos,int limit,int lead,int dig,int sum)
{
    int ans=0;
    if(pos==0) return sum;
    if(!limit&&lead&&dp[pos][sum]) return dp[pos][sum];
    int up=limit?num[pos]:9;
    for(int i=0;i<=up;i++)
    {
        ans+=dfs(pos-1,(i==up)&&limit,i||lead,dig,sum+((i||lead)&&i==dig));
    }
    if(!limit&&lead) dp[pos][sum]=ans;
    return ans;
}

pos代表的是枚举到了数字的第几位,一般是先从高位枚举的;limit表示该位的最大值是9还是只能取该数字的这一位的值,比如3425,我们第一位枚举0~2的时候,第二位可以选0-9,但是如果我们第一位是3的话,那么第二位可以选0~4,依次推下去,如果第1位是3,第二位是4,那么第三位只能选0-2...

lead表示有没有前导零,这个条件需要根据题目来设;dig是要枚举的数位,sum是前几位出现了几次dig,我们保存pos,sum的状态就可以进行转移了

对于一个数a来说,进行处理前要把a的数位拆到数组里,然后用上面的dfs就可以求出每个数位从0-a出现了多少次了,对于一个区间[a,b]每个数位出现了多少次那自然就是每个数位从0-b出现的次数减去从0-(a-1)出现的次数

题解代码

#include <bits/stdc++.h>
#define int long long
#define lowbit(x) ((x)&(-x))
#define endl '\n'
#define double long double
using namespace std;
const int mod=1e9+7;
const int inf=3e18;
double qpow(double a,int b)
{
    double res=1;
    while(b)
    {
        if(b&1) res=res*a;
        a=a*a;
        b>>=1;
    }
    return res;
}
const int N=404;
int dp[20][20],num[20];
int dfs(int pos,int limit,int lead,int dig,int sum)
{
    int ans=0;
    if(pos==0) return sum;
    if(!limit&&lead&&dp[pos][sum]) return dp[pos][sum];
    int up=limit?num[pos]:9;
    for(int i=0;i<=up;i++)
    {
        ans+=dfs(pos-1,(i==up)&&limit,i||lead,dig,sum+((i||lead)&&i==dig));
    }
    if(!limit&&lead) dp[pos][sum]=ans;
    return ans;
}
int sol(int x,int dig)
{
    for(int i=0;i<=15;i++) num[i]=0;
    int len=0;
    while(x)
    {
        num[++len]=x%10;
        x/=10;
    }
    return dfs(len,1,0,dig,0);
}
signed main()
{
    // cin.tie(0);
    // cout.tie(0);
    // ios::sync_with_stdio(0);
    //freopen("in.txt", "r", stdin);
    int a,b;
    cin>>a>>b;
    for(int i=0;i<=9;i++)
    {
        cout<<sol(b,i)-sol(a-1,i)<<" ";
    }
    system("pause");
    return 0;
}

P2657 [SCOI2009] windy 数

不含前导零且相邻两个数字之差至少为 2 的正整数被称为 windy 数。

可以看一下搜索的时候答案是与什么有关,比如搜到了第pos位,那么第pos位就与上一位是有关的(也与下一位有关,但是到了第pos+1位,那么第pos位也会用到),枚举该位可能出现的数的时候就看看是不是与上一位相差至少是2,如果枚举到第pos位,上一位是dig时,发现之前已经搜过了,那么就直接返回即可

#include <bits/stdc++.h>
#define int long long
#define lowbit(x) ((x)&(-x))
#define endl '\n'
#define double long double
using namespace std;
const int mod=1e9+7;
const int inf=3e18;
double qpow(double a,int b)
{
    double res=1;
    while(b)
    {
        if(b&1) res=res*a;
        a=a*a;
        b>>=1;
    }
    return res;
}
int dp[22][22],num[22];
int dfs(int pos,int limit,int lead,int dig)
{
    if(pos==0) return 1;
    if(!limit&&lead&&dp[pos][dig]) return dp[pos][dig];
    int ans=0;
    int up=limit?num[pos]:9;
    for(int i=0;i<=up;i++)
    {
        if(abs(i-dig)<2&&lead) continue;
        ans+=dfs(pos-1,limit&&(i==up),lead||i,i);
    }
    if(!limit&&lead) dp[pos][dig]=ans;
    return ans;
}
int sol(int x)
{
    memset(num,0,sizeof(num));
    int len=0;
    while(x) num[++len]=x%10,x/=10;
    return dfs(len,1,0,0);
}
signed main()
{
    cin.tie(0);cout.tie(0);ios::sync_with_stdio(0);
    //freopen("in.txt", "r", stdin);
    memset(dp,0,sizeof(dp));
    int a,b;
    cin>>a>>b;
    cout<<sol(b)-sol(a-1)<<endl;
    return 0;
}

P4317 花神的数论题 

这题是关于二进制的,这次是要把数按二进制的形式拆到数组里,然后看看第pos位的答案是与什么有关,可以发现只要知道了枚举到第pos位然后这一位的sum之前出现过,就说明这个状态之前搜过了,就可以直接返回了,sum表示的是第pos位之前1出现的个数;

这题打的蒙蒙的,但没想到竟然过了

#include <bits/stdc++.h>
#define int long long
#define lowbit(x) ((x)&(-x))
#define endl '\n'
#define double long double
using namespace std;
const int mod=10000007;
const int inf=3e18;
double qpow(double a,int b)
{
    double res=1;
    while(b)
    {
        if(b&1) res=res*a;
        a=a*a;
        b>>=1;
    }
    return res;
}
int dp[65][65],bit[65];
int dfs(int pos,int limit,int sum)
{
    if(pos==0) return sum;
    if(!limit&&dp[pos][sum]) return dp[pos][sum];
    int up=limit?bit[pos]:1,ans=1;
    for(int i=0;i<=up;i++)
    {
        int tmp=dfs(pos-1,limit&&(i==up),sum+i);
        if(!tmp) continue;
        ans=ans*tmp%mod;
    }
    if(!limit) dp[pos][sum]=ans;
    return ans;
}
int sol(int x)
{
    memset(bit,0,sizeof(bit));
    int len=0;
    while(x) bit[++len]=x%2,x/=2;
    return dfs(len,1,0);
}
signed main()
{
    cin.tie(0);cout.tie(0);ios::sync_with_stdio(0);
    //freopen("in.txt", "r", stdin);
    int n;
    cin>>n;
    cout<<sol(n)<<endl;
    return 0;
}

P6218 [USACO06NOV] Round Numbers S

这个题目其实而上一题差不多,都是二进制,但这个题需要考虑前导零了,因为1的二进制形式是1而不是01,如果是01那就错了,之后按照题目条件来判断就行

#include <bits/stdc++.h>
#define int long long
#define lowbit(x) ((x)&(-x))
#define endl '\n'
#define double long double
using namespace std;
const int mod=10000007;
const int inf=3e18;
double qpow(double a,int b)
{
    double res=1;
    while(b)
    {
        if(b&1) res=res*a;
        a=a*a;
        b>>=1;
    }
    return res;
}
int bit[65];
map<pair<int,int>,int>dp;
int dfs(int pos,int limit,int lead,int sum)
{
    if(pos==0) return sum>=0;
    if(!limit&&lead&&dp[{pos,sum}]) return dp[{pos,sum}];
    int up=limit?bit[pos]:1,ans=0;
    for(int i=0;i<=up;i++)
    {
        int x;
        if(i==0)
        {
            if(lead) x=1;
            else x=0;
        }
        else x=-1;
        ans+=dfs(pos-1,limit&&(i==up),lead||i,sum+x);
    }
    if(!limit&&lead) dp[{pos,sum}]=ans;
    return ans;
}
int sol(int x)
{
    memset(bit,0,sizeof(bit));
    int len=0;
    while(x) bit[++len]=x%2,x/=2;
    return dfs(len,1,0,0);
}
signed main()
{
    cin.tie(0);cout.tie(0);ios::sync_with_stdio(0);
    //freopen("in.txt", "r", stdin);
    int a,b;
    cin>>a>>b;
    cout<<sol(b)-sol(a-1)<<endl;
    return 0;
}

P2106 Sam数 - 数位dp,矩阵快速幂

f[i][j]为第i位上数字为j的个数,那么第i+1位可以取的值就有j-2,j-1,j,j+1,j+2,也就是

f[i+1][j]+=f[i][j-2]+f[i][j-1]+f[i][j]+f[i][j+1]+f[i][j+2];

i太大用矩阵快速幂递推,可以做到log级,f[i+1][0]=f[i][0]+f[i][1]+f[i][2],f[i+1][1]=f[i][0]+f[i][1]+f[i][2]+f[i][3],这样其实也就可以把构造矩阵B看出来了,然后在把f[1]的初始矩阵A推出来就可以快速幂了,之后统计0~9的答案和就可以了,k=1的时候特判一下

#include <bits/stdc++.h>
#define int long long
#define lowbit(x) ((x)&(-x))
#define endl '\n'
#define double long double
using namespace std;
const int mod=1000000007;
const int inf=1e9-1;
const int N=1e7+7;
double qpow(double a,int b)
{
    double res=1;
    while(b)
    {
        if(b&1) res=res*a;
        a=a*a;
        b>>=1;
    }
    return res;
}
bool cmp(int &a,int &b)
{
    return a>b;
}
struct Matrix
{
    int mat[12][12];
    Matrix()
    {
        memset(mat,0,sizeof(mat));
    }
    Matrix operator*(const Matrix &a)
    {
        Matrix res;
        memset(&res,0,sizeof(res));
        for(int i=0;i<=9;i++)
            for(int j=0;j<=9;j++)
            for(int k=0;k<=9;k++)
            res.mat[i][j]=(res.mat[i][j]+mat[i][k]*a.mat[k][j]%mod)%mod;
        return res;
    }
};
Matrix qpow(Matrix a,int b)
{
    Matrix res;
    memset(&res,0,sizeof(res));
    for(int i=0;i<=9;i++) res.mat[i][i]=1;
    while(b)
    {
        if(b&1) res=res*a;
        a=a*a;
        b>>=1;
    }
    return res;
}
int n;
signed main()
{
    cin.tie(0);cout.tie(0);ios::sync_with_stdio(0);
    //freopen("in.txt", "r", stdin);
    cin>>n;
    if(n==1)
    {
        cout<<"10\n";return 0;
    }
    Matrix a,b;
    for(int i=1;i<=9;i++) a.mat[i][0]=1;
    for(int i=0;i<=9;i++)
        for(int j=max(i-2,0LL);j<=min(9LL,i+2);j++)
        b.mat[i][j]=1;
    b=qpow(b,n-1);
    int ans=0;
    a=b*a;
    for(int i=0;i<=9;i++) ans=(ans+a.mat[i][0])%mod;
    cout<<ans<<endl;
    return 0;
}

P2481 [SDOI2010]代码拍卖会 - 数位dp

对于任意一个数x,我们总是可以用类似111...111的数来组成,比如

(来自题解 P2481 【[SDOI2010]代码拍卖会】 - ​ - 洛谷博客)

 而且可以发现对于模数p,1,11,111,111...111这样的数在模p意义下是会有循环节的,设g[i]表示所有有多少类似111...111这样的数模p等于i,然后问题就转化成了从g数组里选不超过9个数模p等于0的方案数,dp[i][j][k]表示前i个数选了k个数这些数的总和模p为j的方案数,假设从g[i+1]中选了o个,那么转移就是dp[i+1][(j+k*i)%p][k+o]+=dp[i][j][k]*c[i][k],c[i][k]表示从g[i]中可重复的选k个,公式就是\binom{k}{g[i]+k-1},可以直接算也可以递推,递推在有些时候耗时更少点,所以代码就先用递推了

用到的点:把数拆成111...111相加的形式,模p意义下有很多数是相同的,循环节,dp计数

题解 P2481 【[SDOI2010]代码拍卖会】 - 瓶中水 - 洛谷博客

#include <bits/stdc++.h>
#define int long long
#define lowbit(x) ((x)&(-x))
#define endl '\n'
#define double long double
using namespace std;
const int mod=999911659;
const int inf=1e9-1;
const int N=1e6+7;
int qpow(int a,int b)
{
    int res=1;
    while(b)
    {
        if(b&1) res=res*a%mod;
        a=a*a%mod;
        b>>=1;
    }
    return res;
}
int getinv(int a){return qpow(a,mod-2);}
int n,p,g[505],a[505],f[505][505][12],inv[12],c[505][12];
int vn;
signed main()
{
    //cin.tie(0);cout.tie(0);ios::sync_with_stdio(0);
    //freopen("in.txt", "r", stdin);
    cin>>n>>p;inv[1]=1;
    if(n<=p) for(int i=1;i<=n;i++) vn=(vn*10+1)%p,g[vn]++;
    else
    {
        int tot=1%p,l,m;
        for(int i=1;i<=p+1;i++)
        {
            if(a[tot]){l=a[tot];m=i-l;break;}//找到循环节,记录位置
            a[tot]=i;g[tot]++;tot=(tot*10+1)%p;//这个数出现的坐标
        }
        for(int i=0;i<p;i++)
        {
            if(a[i]&&a[i]>=l)
            {
                g[i]=(g[i]+(n-a[i])/m)%mod;//加上后面的循环节的个数
                if((a[i]-l+1)%m==(n-l+1)%m) vn=i;//找到与n个1模p的数
            }
        }
    }
    for(int i=2;i<=9;i++) inv[i]=(mod-mod/i)*inv[mod%i]%mod;
    for(int i=0;i<p;i++)
    {
        c[i][0]=1;
        if(!g[i]) continue;
        for(int j=1;j<9;j++,g[i]=(g[i]+1)%mod)
        c[i][j]=((c[i][j-1]*g[i]%mod)*inv[j])%mod;
    }
    f[0][vn][0]=1;
    for(int i=0;i<p;i++)
        for(int j=0;j<p;j++)
        for(int o=0;o<9;o++)
        for(int k=0;k<9-o;k++)
        f[i+1][(j+k*i%p)%p][k+o]=(f[i+1][(j+k*i%p)%p][k+o]+f[i][j][o]*c[i][k]%mod)%mod;
    int ans=0;
    for(int i=0;i<9;i++) ans=(ans+f[p][0][i])%mod;
    cout<<ans<<endl;
    return 0;
}

CF855E Salazar Slytherin's Locket - 数位dp

和模板差不多,需要注意的就是最多需要记录十个位置的状态,每个状态要么是奇数要么是偶数,那就可以用2进制来表示,也就是状态压缩一下,sta就表示十个数的状态,最后返回的时候就是看sta是不是0就可以

CF855E题解 - Ginger_he 的博客 - 洛谷博客

#include <bits/stdc++.h>
using namespace std;
#define ll long long
const int N = 1e5+100;
const double eps=1e-8;
const double pi=acos(-1);
int num[65],q,b,len;
ll f[12][65][1024],l,r;
ll dfs(int pos,int limit,int lead,int sta)
{
    if(pos==0) return !sta;
    if(!limit&&lead&&f[b][pos][sta]!=-1) return f[b][pos][sta];
    ll res=0,up=limit?num[pos]:(b-1);
    for(int i=0;i<=up;i++)
    {
        res+=dfs(pos-1,(i==up)&&limit,i||lead,(i||lead)?(sta^(1<<i)):0);
    }
    if(!limit&&lead) f[b][pos][sta]=res;
    return res;
}
ll sol(ll x)
{
    len=0;
    while(x) num[++len]=x%b,x/=b;
    return dfs(len,1,0,0);
}
signed main()
{
    ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
    memset(f,-1,sizeof(f));
   cin>>q;
   while(q--)
   {
       cin>>b>>l>>r;
       ll ans=sol(r)-sol(l-1);
       cout<<ans<<endl;
   }
    return 0;
}
//

SP10606 BALNUM - Balanced Numbers 

 和昨天做的那道是一样的,用一个二进制数来记录0-9的状态,为了记录偶数出现奇数次以及技术出现偶数次还需要在用一个数记录,所以记忆化搜索的时候也是多了一个状态,注意到这个地方就可以了

#include <bits/stdc++.h>
using namespace std;
#define int long long
const int N = 1e5+100;
const double eps=1e-8;
const double pi=acos(-1);
int f[12][1024][1024],num[65],t,len;
int dfs(int pos,int limit,int lead,int sta,int chk)
{
    if(pos==0)
    {
//        stack<int>st;
//        int xx=chk;
//        while(xx) st.push(xx%2),xx/=2;
//        while(!st.empty()){cout<<st.top();st.pop();}
//        cout<<endl;
//        cout<<sta<<" "<<chk<<endl;
        return (sta==chk);
    }
    if(!limit&&!lead&&f[pos][sta][chk]!=-1) return f[pos][sta][chk];
    int res=0,up=limit?num[pos]:9;
    for(int i=0;i<=up;i++)
    {
        res+=dfs(pos-1,i==up&&limit,lead&&(i==0),(lead&&(i==0))?0:(sta^(1<<i)),(lead&&(i==0))?0:(chk|((i&1)?0:(1<<i))));
    }
    if(!limit&&!lead) f[pos][sta][chk]=res;
    return res;
}
int sol(int x)
{
    len=0;
    while(x) num[++len]=x%10,x/=10;
    return dfs(len,1,1,0,0);
}
signed main()
{
    ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
    cin>>t;
    memset(f,-1,sizeof(f));
    while(t--)
    {
        int a,b;
        cin>>a>>b;
        int ans=sol(b)-sol(a-1);
        cout<<ans<<endl;
    }
    return 0;
}
//

P4124 [CQOI2016]手机号码

这题其实不难,但是让自己的马虎和不知道为什么错的写法搞心态了,三次出现,就让a为上一位出现的数,让b位上两位出现的数,8和4分别用一个变量表示,再用一个sco变量判断是否已经合法就可以了

#include <bits/stdc++.h>
using namespace std;
#define int long long
const int N = 1e5+100;
const double eps=1e-8;
const double pi=acos(-1);
int f[13][10][10][2][2][2],num[13];
int dfs(int pos,int a,int b,int eight,int four,int sco,int limit)
{
    if(eight&&four) return 0;
    if(pos==0) return sco;
    if(!limit&&f[pos][a][b][eight][four][sco]!=-1) return f[pos][a][b][eight][four][sco];
    int up=limit?num[pos]:9,res=0;
    for(int i=0;i<=up;i++)
    {
        //if(i==eight&&four||i==four&&eight) return 0;
        res+=dfs(pos-1,i,a,eight||(i==8),four||(i==4),sco||(i==a&&i==b),limit&&(i==up));
    }
    if(!limit) f[pos][a][b][eight][four][sco]=res;
    return res;
}
int sol(int x)
{
    int len=0;
    while(x) num[++len]=x%10,x/=10;
    if(len!=11) return 0;//还是要判断的,因为可能会有L-1的情况是10位数
    int res=0;
    for(int i=1;i<=num[len];i++)//处理第一位会有前导0的情况
        res+=dfs(len-1,i,-1,i==8,i==4,0,i==num[len]);
    return res;
}
signed main()
{
    ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
    int l,r;
    memset(f,-1,sizeof(f));
    cin>>l>>r;
    int ans=sol(r)-sol(l-1);
    cout<<ans<<endl;
    return 0;
}
//

1073E - Segment Sum 数位dp

需要dfs参数有limit,lead,pos,sta(各个位的状态,1表示含有这个位的数,0表示没有),然后发现f[pos][sta]会吞掉许多状态,然后就又加了个sum表示pos这一位,状态为sta,数为sum时的状态,但是这样的状态好像又过于多了,然后W,R,T,M的错误全出了一遍还是没有做出来,只能去看题解,,

还是一开始的状态f[pos][sta],发现对于pos位,如果选的数为x,那么这一位对总答案的贡献就是x*pow[pos-1]*cnt,pow数组是10的平方,cnt是后续合法的个数,那么f[pos][sta]设为从pos开始后续所有合法状态的和,但是还是一样的问题,只有一个f数组是记录不了所有的状态的,所以还需要一个数组g,g[pos][sta]表示后续有多少合法的状态数,转移的时候

g[pos][sta]=sum(g[pos-1][sta']),sta'表示pos-1位所有满足条件的位,

f[pos][sta]=sum(f[pos][sta]+x*pow[pos-1]*g[pos-1][sta']+f[pos-1][sta']),

边界条件是如果sta满足条件的话g[pos][sta]就是1,f无论怎样都是0;

#include <bits/stdc++.h>
using namespace std;
#define int long long
const int N = 1e6+5;
const int mod=998244353;
const double eps=1e-8;
const double pi=acos(-1);
int qpow(int a,int b)
{
    int res=1;
    while(b)
    {
        if(b&1) res=res*a%mod;
        a=a*a%mod;
        b>>=1;
    }
    return res;
}
int getinv(int a){return qpow(a,mod-2);}
int k,num[22],cnt,p[20];
pair<int,int>f[20][1024];
pair<int,int> dfs(int pos,int limit,int lead,int sta)
{
    if(pos==0)
    {
        return {__builtin_popcount(sta)<=k,0};
    }
    if(!limit&&!lead&&f[pos][sta].first!=-1) return f[pos][sta];
    int up=limit?num[pos]:9;
    pair<int,int> res(0,0);
    for(int i=0;i<=up;i++)
    {
        pair<int,int>tmp=dfs(pos-1,i==up&&limit,!i&&lead,!i&&lead?0:sta|(1<<i));
        res=pair<int,int>((res.first+tmp.first)%mod,(((tmp.first*i%mod)*p[pos-1]%mod+tmp.second)%mod+res.second)%mod);
    }
    if(!limit&&!lead) f[pos][sta]=res;
    return res;

}
int sol(int x)
{
    int len=0;
    while(x) num[++len]=x%10,x/=10;
    for(int i=0;i<=18;i++)
        for(int j=0;j<=1023;j++) f[i][j].first=f[i][j].second=-1;
    return dfs(len,1,1,0).second;
}
signed main()
{
    ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
    int l,r;
    p[0]=1;
    for(int i=1;i<=18;i++) p[i]=p[i-1]*10%mod;
    cin>>l>>r>>k;
    int ans=((sol(r)-sol(l-1))%mod+mod)%mod;
    cout<<ans<<endl;
    return 0;
}
//

Beautiful numbers 

 1~9的最小公倍数是2520,如果一个数sum取余多个数的最小公倍数是0,那么他取余这些数也一定是0,所以记录状态时只需要记录最小公倍数就可以了,更进一步发现2520只有40多个因子,而对题目有用的也只是这些因子而已,所以只记录这些因子就可以;sum%2520和sum在题目中的意义是一样的,所以sum这一维可以缩到2520,所以总的状态就是dp[pos][sum][book[lcm]],pos是位置,最大19,sum是取余后的sum,最大2520,book[lcm]是因子的编号最大48,所以不会爆内存

题解 CF55D 【Beautiful numbers】 - _Kurumi_ 的博客 - 洛谷博客

#include <bits/stdc++.h>
using namespace std;
#define endl '\n'
#define int long long
const int N = 2e5+5;
const int mod=2520;
const int inf=1e18;
const double eps=1e-8;
const double pi=acos(-1);
int qpow(int a,int b)
{
    int res=1;
    while(b)
    {
        if(b&1) res=res*a%mod;
        a=a*a%mod;
        b>>=1;
    }
    return res;
}
int getinv(int a){return qpow(a,mod-2);}
int Lcm(int a,int b){return a*b/__gcd(a,b);}
int t,num[22],len,f[22][2555][55],book[2525];
void init()
{
    memset(f,-1,sizeof(f));
    int x=0;
    for(int i=1;i<=mod;i++)
        if(mod%i==0) book[i]=++x;
}
int dfs(int pos,int limit,int lcm,int sum)
{
    if(pos==0)
    {
        //cout<<sum<<" "<<lcm<<endl;
        return sum%lcm==0;
    }
    if(!limit&&f[pos][sum][book[lcm]]!=-1) return f[pos][sum][book[lcm]];
    int up=limit?num[pos]:9,res=0;
    for(int i=0;i<=up;i++)
    {
        int nsum=(sum*10+i)%mod;
        int nlcm=lcm;
        if(i) nlcm=Lcm(nlcm,i);
        res+=dfs(pos-1,i==up&&limit,nlcm,nsum);
    }
    if(!limit) f[pos][sum][book[lcm]]=res;
    return res;
}
int sol(int x)
{
    len=0;
    while(x) num[++len]=x%10,x/=10;
    return dfs(len,1,1,0);
}
signed main()
{
    ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
    //freopen("in.txt","r",stdin);
    cin>>t;
    init();
    while(t--)
    {
        int l,r;
        cin>>l>>r;
        //cout<<sol(r)<<" "<<sol(l-1)<<endl;
        int ans=sol(r)-sol(l-1);
        cout<<ans<<endl;
    }
    return 0;
}
//

P4127 [AHOI2009]同类分布 

参数有pos,limit,sum(各个数位的和),ori(原数),然后f[pos][sum][ori],但是由于ori太大所以要进行取模处理,但是模数是在变化的,因为sum一直是变化的,所以直接枚举模数,最后的判断条件就是ori==0&&sum==mod?1:0,然后这题就可以做了

P4127 同类分布【数位dp】 - Mathison 的博客 - 洛谷博客

#include <bits/stdc++.h>
using namespace std;
#define endl '\n'
#define int long long
const int N = 2e5+5;
int mod=998244353;
const int inf=1e18;
const double eps=1e-8;
const double pi=acos(-1);
int qpow(int a,int b)
{
    int res=1;
    while(b)
    {
        if(b&1) res=res*a%mod;
        a=a*a%mod;
        b>>=1;
    }
    return res;
}
int getinv(int a){return qpow(a,mod-2);}
int Lcm(int a,int b){return a*b/__gcd(a,b);}
int num[20],f[20][200][200];
int dfs(int pos,int limit,int sum,int ori)
{
    if(pos==0)
    {
        if(sum==0) return 0;
        return ori==0&&sum==mod?1:0;
    }
    if(!limit&&f[pos][sum][ori]!=-1) return f[pos][sum][ori];
    int up=limit?num[pos]:9,res=0;
    for(int i=0;i<=up;i++)
    {
        res+=dfs(pos-1,i==up&&limit,sum+i,(ori*10+i)%mod);
    }
    if(!limit) f[pos][sum][ori]=res;
    return res;
}
int sol(int x)
{
    int len=0;
    while(x) num[++len]=x%10,x/=10;
    int res=0;
    for(mod=1;mod<=9*len;mod++)
    {
        memset(f,-1,sizeof(f));
        res+=dfs(len,1,0,0);
    }
    return res;
}
signed main()
{
    ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
    //freopen("in.txt","r",stdin);
    int a,b;
    cin>>a>>b;
    //cout<<sol(b)<<" "<<sol(a)<<endl;
    int ans=sol(b)-sol(a-1);
    cout<<ans<<endl;
    return 0;
}
//

2022广州 M - XOR Sum 数位dp

给定n,m,k(0<=n<=1e15,0<=m<=1e12,1<=k<=18),

求长度为k的数组a,ai为[0,m]的整数,满足\sum_{i=1}^{k}\sum_{j=1}^{i-1}a_{i}\bigoplus a_{j}=n的方案数

这个公式其实就是数组中的数两两异或,那么对于某一个二进制位bit来说,这位上为1的数有x个,那么这一位的贡献就是x*(k-x)*(1<<bit),把n化成二进制的形式从高位到低位开始抵消,如果n的这一位是1且有r个,那么就需要让一些数来抵消他,如果没有抵消完那就让剩下的nr个下放到低位中去,下放到低位就要乘以2了,所以r是有限制的,k最大是18,每次最大可以产生9*9=81个1,假设这一位个数是x,那么(x-81)*2<x,x<162,表示这次取了81下一次余数小于81,x最大是162,所以当x也就是上文中的r大于162的话就没啥意义了,因为就算后面取最大也抵消不了n了,这其实是和二进制差不多,1000是要比111大的;

考虑数位dp,dp[x][r][cur]表示现在是m的从高到低第x个二进制位,余数有r个(当前这位需要r个1才可以抵消),当前有cur个数到达了上界可以产生的方案数,分两种情况,如果a[x]==0,那么那些取到上界的数就只能取0,没有取到上界的可以取1,枚举去了多少1,可以消去的数就是i*(k-i);

如果a[x]==1,那么那些取到上界的或没取到上界的都可以取0或1,二重枚举,可以消去的数就是(i+j)*(k-i-j);

2022 China Collegiate Programming Contest (CCPC) Guangzhou Onsite M. XOR Sum(数位dp 数位背包)_Code92007的博客-CSDN博客

#include<bits/stdc++.h>
#define int long long
#define ll __int128
#define lowbit(x) ((x)&(-x))
#define endl '\n'
#define pause system("pause")
using namespace std;
const int N=1e6+5;
const int mod=1e9+7;
const int inf=1e18;
int qpow(int a,int b)
{
    int res=1;
    while(b)
    {
        if(b&1) res=res*a%mod;
        a=a*a%mod;
        b>>=1;
    }
    return res;
}
int getinv(int a){return qpow(a,mod-2);}
int dp[64][258][20],a[64],c[66][66],n,m,k,M=200;
int dfs(int x,int r,int cur)
{
    if(x<0) return r==0;
    if(r>M) return 0;
    if(dp[x][r][cur]!=-1) return dp[x][r][cur];
    int ans=0;
	int nb=(x==0?0:(n>>(x-1)&1));
    if(a[x]==0)
    {
        for(int i=0;i<=k-cur;i++)
        {
            int nr=r-i*(k-i);
            if(nr<0) continue;
            ans=(ans+dfs(x-1,(nr<<1)|nb,cur)*c[k-cur][i]%mod)%mod;
        }
    }
    else
    {
        for(int i=0;i<=cur;i++)
        {
            for(int j=0;j<=k-cur;j++)
            {
                int nr=r-(i+j)*(k-i-j);
                if(nr<0) continue;
                ans=(ans+(dfs(x-1,(nr<<1)|nb,i)*c[cur][i]%mod)*c[k-cur][j]%mod)%mod;
            }
        }
    }
    return dp[x][r][cur]=ans;
}
int cal()
{
    if(k==1)return !n;
	if(!m)return !n;
    memset(dp,-1,sizeof(dp));
    int len=0,x=m;
    while(x)
    {
        a[len++]=x%2;
        x/=2;
    }
    int rn=0;
    for(int i=len;i<=50;i++)
    {
        if(n>>i&1)
        {
            rn+=1<<(i-len);
            if(rn>=M) return 0;
        }
    }
    return dfs(len,rn,k);
}
signed main()
{
    ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
    c[0][0]=1;
    for(int i=1;i<=64;i++)
    {
        c[i][0]=c[i][i]=1;
        for(int j=1;j<i;j++)
            c[i][j]=(c[i-1][j-1]+c[i-1][j])%mod;
    }
    cin>>n>>m>>k;
    int ans=cal();
    cout<<ans<<endl;
    return 0;
}

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

killer_queen4804

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值