数位DP总结

数位DP

感谢大佬的分享
凌乱之风

模板

注意数位DP只与位数有关,当给出的数的范围很大但知道位数的情况下,可以考虑用数位DP

\(dfs\)写法

int dfs(int pos, int pre, int lead, int limit,其它记录转移状态的参数) {
    if (!pos) {
        边界条件
    }
    if (!limit && !lead && dp[pos][pre] != -1) return dp[pos][pre];
    int res = 0, up = limit ? a[pos] : 无限制位;
    for (int i = 0; i <= up; i ++) {
        if (不合法条件) continue;
        res += dfs(pos - 1, 未定参数, 未定参数, limit && i == up);
    }
    return limit ? res : dp[pos][pre] = res;
}
int cal(int x) {
    memset(dp, -1, sizeof dp);
    len = 0;
    while (x) a[++ len] = x % 进制, x /= 进制;
    return dfs(len, 未定参数, 1, 1);
}
int main() {
    cin >> l >> r;
    cout << cal(r) - cal(l - 1) << endl;
}

例题

1.Windy

Windy 定义了一种 Windy 数:不含前导零且相邻两个数字之差至少为 2的正整数被称为 Windy 数。

Windy 想知道,在 A和 B 之间,包括 A和 B,总共有多少个 Windy 数?

#include <bits/stdc++.h>
using namespace std;
vector<int>v;
int dp[15][15];
int dfs(int pos,int pre,int lead,int limit)
{
    if(pos==-1) return 1;
    if(!limit&&!lead&&dp[pos][pre]!=-1) return dp[pos][pre];
    
    int res=0,mx=limit?v[pos]:9;
    for(int i=0;i<=mx;i++)
    {
        if(abs(i-pre)<2)  continue;
        if(lead&&i==0) res+=dfs(pos-1,pre,1,limit&&i==mx);
        else res+=dfs(pos-1,i,0,limit&&i==mx);
    }
    return limit?res:dp[pos][pre]=res;
}
int cal(int x)
{
    //memset(dp,-1,sizeof dp);
    v.clear();
    while(x)
    {
        v.push_back(x%10);
        x/=10;
    }
    int n=v.size();
    return dfs(n-1,11,1,1);
}
int main()
{
    ios::sync_with_stdio(false);
    cin.tie(0);
    memset(dp,-1,sizeof dp);
    int a,b;
    cin>>a>>b;
    cout<<cal(b)-cal(a-1)<<'\n';
    return 0;
    
}

2.数字游戏

由于科协里最近真的很流行数字游戏。

某人又命名了一种取模数,这种数字必须满足各位数字之和 mod N 为 0。

现在大家又要玩游戏了,指定一个整数闭区间\([a,b]\)问这个区间内有多少个取模数。

#include <bits/stdc++.h>
using namespace std;
int p;
vector<int>v;
int dp[15][1005];
int dfs(int pos,int sum,int limit){
    if(pos==-1) return sum%p==0;
    if(!limit&&dp[pos][sum]!=-1)return dp[pos][sum];
    int res=0,mx=limit?v[pos]:9;
    for(int i=0;i<=mx;i++)
    {
        res+=dfs(pos-1,sum+i,limit&&i==mx);
    }
    return limit?res:dp[pos][sum]=res;
}
int cal(int x){
    memset(dp,-1,sizeof dp);
    //注意初始赋值为-1,因为可能有些状态不符合条件答案为0,就可以忽略,否则可能会T
    v.clear();
    while(x)
    {
        v.push_back(x%10);
        x/=10;
    }
    int n=v.size();
    return dfs(n-1,0,1);
}
int main()
{
    ios::sync_with_stdio(false);
    cin.tie(0);
    int a,b;
    while(cin>>a>>b>>p){
    cout<<cal(b)-cal(a-1)<<'\n';
    }
    
}

3. 度的数量

求给定区间 [X,Y] 中满足下列条件的整数个数:这个数恰好等于 K 个互不相等的 B 的整数次幂之和。

#include <bits/stdc++.h>
#define ll long long
using namespace std;
vector<int>v;
ll dp[35][35];
ll dfs(int pos,int cnt,int limit,int b,int k)
{
    if(pos==-1) return cnt==k;
    if(!limit&&dp[pos][cnt]!=-1) return dp[pos][cnt];
    ll res=0;
    int mx=limit?v[pos]:b-1;
    for(int i=0;i<=mx;i++)
    {
        if((i==1&&cnt==k)||i>1) continue; //i>1保证互不相等
        res+=dfs(pos-1,cnt+(i==1),limit&&i==mx,b,k);
    }
    return limit?res:dp[pos][cnt]=res;
}
ll cal(int x,int b,int k)
{
    memset(dp,-1,sizeof dp);
    v.clear();
    while(x)
    {
        v.push_back(x%b);
        x/=b;
    }
    int n=v.size();
    return dfs(n-1,0,1,b,k);
}
int main()
{
    ios::sync_with_stdio(false);
    cin.tie(0);
    int x,y;
    int k,b;
    cin>>x>>y>>k>>b;
    cout<<cal(y,b,k)-cal(x-1,b,k)<<'\n';
    return 0;

}

4. 恨7不成妻

如果一个整数符合下面三个条件之一,那么我们就说这个整数和 7 有关:

  1. 整数中某一位是 7;
  2. 整数的每一位加起来的和是 7 的整数倍;
  3. 这个整数是 7 的整数倍。

现在问题来了:吉哥想知道在一定区间内和 7 无关的整数的平方和。

#include <bits/stdc++.h>
using namespace std;
constexpr long long mod = 1e9+7;
using ll=long long;
ll Power[25];
vector<int>v;
struct dp{
     ll cnt,sum,sum2;
}f[20][7][7];

dp dfs(int pos,int sum,int num,int limit){

    if(pos==-1) return (dp){sum&&num,0,0};
    if(!limit&&f[pos][sum][num].cnt!=-1)  return f[pos][sum][num];
    int mx=limit?v[pos]:9;
    dp ans{0,0,0};
    for(int i=0;i<=mx;i++)
    {
        if(i==7) continue;
        dp tmp=dfs(pos-1,(sum+i)%7,(num*10+i)%7,limit&&(i==mx));
        
        ll k=i*Power[pos]%mod;
        ans.cnt=(ans.cnt+tmp.cnt)%mod;//tmp.cnt存的是第pos-1位到最后一位满足条件的数的值
        ans.sum=((ans.sum+tmp.cnt*k%mod)%mod+tmp.sum)%mod;
        /*
        比如1222 1333 1444
        现在后3位的和已经知道了,那么当前位是1的和就是 1000*3+(222+333+444)
        */
        ans.sum2=((ans.sum2+(k*k%mod*tmp.cnt%mod+tmp.sum2)%mod)%mod+tmp.sum*k%mod*2%mod)%mod;
        /*
        以1222为例,1222^2=1000^2+2*1000*222+222^2
        */

    }
    return limit?ans:f[pos][sum][num]=ans;
}

ll cal(ll x)
{
    v.clear();
    while(x)
    {
        v.push_back(x%10);
        x/=10;
    }
    int n=v.size();
    return dfs(n-1,0,0,1).sum2%mod;
}
int main()
{
    
    ios::sync_with_stdio(false);
    cin.tie(0);
    int T;
    cin>>T;
     Power[0]=1;
    for(int i=1;i<=20;i++)
    Power[i]=Power[i-1]*10%mod;
    memset(f,-1,sizeof f);
    while(T--)
    {      
        ll l,r;
        cin>>l>>r;
       cout<<(cal(r)-cal(l-1)+mod)%mod<<'\n';   
    }
}


6.同类分布

给出两个数a,b,求出[a,b]中各位数字之和能整除原数的数的个数。\(1\le a,b\le 10^{18}\)

#include <bits/stdc++.h>
#define ll long long
using namespace std;

ll dp[20][170][170];
vector<int>v;
ll dfs(int pos,int sum,int rem,int limit,int mod){
    if(sum+9*(pos+1)<mod) return 0;
    if(pos==-1) return sum==mod&&rem==0;
    if(!limit&&dp[pos][sum][rem]!=-1) return dp[pos][sum][rem];
   
    ll res=0;
    int mx=limit?v[pos]:9;
    for(int i=0;i<=mx&&i+sum<=mod;i++){
        res+=dfs(pos-1,sum+i,(rem*10+i)%mod,limit&&i==mx,mod);
    }
    return limit?res:dp[pos][sum][rem]=res;
}
ll cal(ll x){
    v.clear();
    while(x){
        v.push_back(x%10);
        x/=10;
    }
    int n=v.size();
    ll res=0;
    for(int mod=1;mod<=n*9;mod++)  //枚举模数sum
    {
         memset(dp,-1,sizeof dp);
         res+=dfs(n-1,0,0,1,mod);
    }
    return res;
}
int main(){
    ios::sync_with_stdio(false);
    cin.tie(0);
    ll l,r;
    cin>>l>>r;
    cout<<cal(r)-cal(l-1)<<'\n';
}

7.花神的数论题

\(sum(i)\)表示\(i\)二进制中\(1\)的个数,给定\(N\),求\(\prod_{i=1}^Nsum(i)\)

转化为\(\prod_{i=1}^{bit(N)}i^{cnt[i]}\)

/*
设dp[i][j][k]为在前i位中,已经有j个1,且该数总计有k个1的数的个数。
还有组合数学的做法。
*/

#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int mod=10000007;
ll dp[64][64][64];
vector<int>v;
ll dfs(int pos,int cnt,int num,int limit){
    if(pos==-1) return cnt==num;
    if(!limit&&dp[pos][cnt][num]!=-1) return dp[pos][cnt][num];
   
    ll res=0;
    int mx=limit?v[pos]:1;
    for(int i=0;i<=mx;i++){
        res+=dfs(pos-1,cnt+(i==1),num,limit&&i==mx);
    }
    return limit?res:dp[pos][cnt][num]=res;
}
ll qmi(ll x,ll y)
{
    ll res=1;
    while(y)
    {
        if(y&1) res=res*x%mod;
        x=x*x%mod;
        y>>=1;
    }
    return res;
}
ll cal(ll x){
    v.clear();
    while(x){
        v.push_back(x%2);
        x/=2;
    }
    int n=v.size();
    ll res=1;
    for(int i=1;i<=n;i++)
    {
        memset(dp,-1,sizeof dp);
        res=res*qmi(i,dfs(n-1,0,i,1))%mod;
    }
    return res;
    
}
int main(){
    ios::sync_with_stdio(false);
    cin.tie(0);
    ll n;
    cin>>n;
    cout<<cal(n)<<'\n';
    
}

8.J. Junior Mathematician

定义函数\(f(x)=\sum_{i=1}^{k-1}\sum_{j=i+1}^kd(x,i)\times d(x,j)\),其中\(d(x,i)\)\(x\)在十进制下第\(i\)位的数字,\(k\)\(x\)在十进制下的位数。

定义一个数是好的数字为\(x \equiv f(x) \pmod m\)

给定\(L\)\(R\),求\([L,R]\)中好的数的个数

\(10\le L\le R\le 10^{5000}\) \(2\le m\le 60\)

答案对\(10^9+7\)取模

\(x \equiv f(x) \pmod m\)转换为\(x-f(x) \equiv 0 \pmod m\)

这样记录\(x \pmod m\)\(f(x) \pmod m\)的维数可以合并为1维

作差这个思想很常见

#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int mod=1e9+7;
int pw[5005]; //这个不要开ll
ll dp[5005][65][65];
int v[5005];
int P;
inline ll dfs(int pos,int sum,int rem,int limit)
{
    if(pos==-1) return rem==0;
    if(!limit&&dp[pos][sum][rem]!=-1) return dp[pos][sum][rem];
    ll res=0;
    int mx=limit?v[pos]:9;
    for(int i=0;i<=mx;i++)
    res+=dfs(pos-1,(sum+i)%P,((rem+pw[pos+1]*i-sum*i)%P+P)%P,limit&&i==mx);
    /*
    例如:
    1234当枚举到第4位时
    12340-f(12340)=12340-(f(123)+(1+2+3)*4)=12300-f(123)+40-(1+2+3)*4=rem+i*pw[pow+1]-sum(i)
    */
    res%=mod;

    return limit?res:dp[pos][sum][rem]=res;
}
inline int cal(string s,bool flag)
{
    
    int n=s.size();
    //用memset会T
    for(int a=0;a<=n;a++)
        for(int b=0;b<=P;b++)
            for(int c=0;c<=P;c++) dp[a][b][c]=-1;
           
    for(int i=n-1;i>=0;i--) v[n-1-i]=s[i]-'0';
    if(flag)
    {
        v[0]--;
        for(int i=0;i<n-1;i++)
        { 
            if(v[i]<0) v[i]+=10,v[i+1]--;
            else break;
        }
        if(v[n-1]==0) n--;
    }
    return dfs(n-1,0,0,1);
}
int main()
{
    ios::sync_with_stdio(false);
    cin.tie(0);
    int T;
    cin>>T;
    while(T--){

        string l,r;
        cin>>l>>r;
        cin>>P;
        int len=r.size();
        pw[1]=1;
        for(int i=2;i<=len;i++) pw[i]=pw[i-1]*10%P;
        cout<<(cal(r,0)-cal(l,1)+mod)%mod<<'\n';
    }
}
posted @ 2022-02-10 20:04  Arashimu  阅读(41)  评论(0编辑  收藏  举报