「kuangbin带你飞」专题十五 数位DP

Posted luowentao

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了「kuangbin带你飞」专题十五 数位DP相关的知识,希望对你有一定的参考价值。

传送门

A.CodeForces - 55D Beautiful numbers

题意

一个正整数是 漂亮数 ,当且仅当它能够被自身的各非零数字整除。我们不必与之争辩,只需计算给定范围中有多少个漂亮数。

思路

因为问你的是一段区间内有多少数能整除他的所有非零数位

1-9,1,一定能被任何正整数整除,1-9的最小公倍数为2520

而1-2520中真正是1-9中的最小公倍数的只有48个

dp i j k:因为dp25,2520,[2520]开不下,所以我们要进行适当离散化

index[]数组标记1-2520,哪些是真正的最小公倍数,是第几个

所以dp[i][j][k]:长度为i的数,该数对2520取模为j,它的数位和的最小公倍数是第k个->index[i]

#include<algorithm>
#include <iostream>
#include <stdlib.h>
#include <string.h>
#include  <stdio.h>
#include   <math.h>
#include   <time.h>
#include   <vector>
#include   <bitset>
#include    <queue>
#include      <map>
#include      <set>
using namespace std;
typedef long long ll;
#define pp pair<int,int>
const int mod=2520;
const int maxn=1e6+50;
const ll inf=0x3f3f3f3f3f3f3f3fLL;
int gcd(int a,int b){if(b==0)return a;else return gcd(b,a%b);}
int lcm(int a,int b){return (a*b)/gcd(a,b);}
int islcm[mod+10];
int num[200];
ll dp[200][2550][50];
void init(){
    int num=0;
    for(int i=1;i<=2520;i++){
        if(2520%i==0)islcm[i]=++num;
    }
}
ll dfs(int pos,int nownum,int nowlcm,bool limit){
    if(pos==-1)return nownum%nowlcm==0;
    if(!limit&&dp[pos][nownum][islcm[nowlcm]]!=-1)return dp[pos][nownum][islcm[nowlcm]];
    int up=limit?num[pos]:9;
    ll ans=0;
    for(int i=0;i<=up;i++){
        int nowsum=(nownum*10+i)%mod;
        int nowl=nowlcm;
        if(i)nowl=lcm(nowl,i);
        ans+=dfs(pos-1,nowsum,nowl,limit&&i==num[pos]);
    }
    if(!limit)dp[pos][nownum][islcm[nowlcm]]=ans;
    return ans;
}
ll ac(ll x){
    ll pos=0;
    while(x){
        num[pos++]=x%10;
        x/=10;
    }
    return dfs(pos-1,0,1,true);
}
int main()
{
    std::ios::sync_with_stdio(false);
    std::cin.tie(0);
    std::cout.tie(0);
    memset(dp,-1,sizeof(dp));
    init();
    int t;
    cin>>t;
    while(t--){
        ll n,m;
        cin>>n>>m;
        cout<<ac(m)-ac(n-1)<<endl;
    }
    return 0;
}

B.HDU - 4352 XHXJ‘s LIS

题意

问你一个longlong范围内(a,b)中每一位的数字组成的最长严格递增子序列(LIS)长度为K的个数

题意

首先 关系到数字和位数 数位DP

然后LIS 最长递增子序列利用二分查找当前递增子序列中是否有大于它的数,如果有替换最小的哪一个,如果没有就加入长度+1;可以利用状态压缩保存当前的最长递增子序列的类型;因为数字最多十个所以类型最多长度为10,用二进制的1表示当前位数的这个数在最长递增子序列中;

然后注意前导0的处理

#include<algorithm>
#include <iostream>
#include <stdlib.h>
#include <string.h>
#include  <stdio.h>
#include   <math.h>
#include   <time.h>
#include   <vector>
#include   <bitset>
#include    <queue>
#include      <map>
#include      <set>
using namespace std;
typedef long long ll;
#define pp pair<int,int>
const ll mod=998244353;
const int maxn=1e6;
const ll inf=0x3f3f3f3f3f3f3f3fLL;
int gcd(int a,int b){while(b){int t=a%b;a=b;b=t;}return a;}
int lcm(int a,int b){return a*b/gcd(a,b);}
int k;
int num[21];
ll dp[21][1<<11][11];
int numcnt(int x){
    int cnt=0;
    for(int i=0;i<10;i++){
        if(x&(1<<i))cnt++;
    }
    return cnt;
}
int numchange(int x,int p){
    for(int i=p;i<=9;i++){
        if(x&(1<<i))return (x^(1<<i))|(1<<p);
    }
    return x|(1<<p);
}
ll dfs(int pos,int nownum,bool limit,bool zero){
    if(pos==-1)return numcnt(nownum)==k;
    if(!limit&&dp[pos][nownum][k]!=-1)return dp[pos][nownum][k];
    int up=limit?num[pos]:9;
    ll ans=0;
    for(int i=0;i<=up;i++){
        ans+=dfs(pos-1,(zero&&i==0)?0:numchange(nownum,i),limit&&i==num[pos],zero&&i==0);
    }
    if(!limit)dp[pos][nownum][k]=ans;
    return ans;
}
ll ac(ll x){
    ll pos=0;
    while(x){
        num[pos++]=x%10;
        x/=10;
    }
    return dfs(pos-1,0,true,true);
}
int main()
{
    std::ios::sync_with_stdio(false);
    std::cin.tie(0);
    std::cout.tie(0);
    memset(dp,-1,sizeof(dp));
    int t;
    cin>>t;
    ll a,b;
    int cnt=0;
    while(t--){
        cin>>a>>b>>k;
        cout<<"Case #"<<++cnt<<": "<<ac(b)-ac(a-1)<<endl;
    }
    return 0;
}

C.HDU - 2089 不要62

题意

中文题

思路

数位DP模板题

#include<algorithm>
#include <iostream>
#include <stdlib.h>
#include <string.h>
#include  <stdio.h>
#include   <math.h>
#include   <time.h>
#include   <vector>
#include   <bitset>
#include    <queue>
#include      <map>
#include      <set>
using namespace std;
typedef long long ll;
#define pp pair<int,int>
const ll mod=998244353;
const int maxn=1e6+50;
const ll inf=0x3f3f3f3f3f3f3f3fLL;
int gcd(int a,int b){while(b){int t=a%b;a=b;b=t;}return a;}
int num[20];
int dp[20][2];
int dfs(int pos,int pre,int sta,bool first){
    if(pos==-1)return 1;
    if(!first&&dp[pos][sta]!=-1)return dp[pos][sta];
    int up=first?num[pos]:9;
    int ans=0;
    for(int i=0;i<=up;i++){
        if(pre==6&&i==2)continue;
        if(i==4)continue;
        ans+=dfs(pos-1,i,i==6,first&&i==num[pos]);
    }
    if(!first)dp[pos][sta]=ans;
    return ans;
}
int ac(int x){
    int pos=0;
    while(x){
        num[pos++]=x%10;
        x/=10;
    }
    return dfs(pos-1,-1,0,true);
}
int main()
{
    std::ios::sync_with_stdio(false);
    std::cin.tie(0);
    std::cout.tie(0);
    int n,m;
    memset(dp,-1,sizeof(dp));
    while(cin>>n>>m&&(n&&m)){
        cout<<ac(m)-ac(n-1)<<endl;
    }
    return 0;
}

D.HDU - 3555 Bomb

题意

求1-n中出现49的数的个数,不要62的反例

思路1

不要62的反解 注意ac(n)算的是0-n之前的数其中包括了0 因为多减去了一个0所以结果要再加上0

#include<algorithm>
#include <iostream>
#include <stdlib.h>
#include <string.h>
#include  <stdio.h>
#include   <math.h>
#include   <time.h>
#include   <vector>
#include   <bitset>
#include    <queue>
#include      <map>
#include      <set>
using namespace std;
typedef long long ll;
#define pp pair<int,int>
const ll mod=998244353;
const int maxn=1e6+50;
const ll inf=0x3f3f3f3f3f3f3f3fLL;
int gcd(int a,int b){while(b){int t=a%b;a=b;b=t;}return a;}
int num[50];
ll dp[50][2];
ll dfs(int pos,int pre,int sta,bool first){
    if(pos==-1)return 1;
    if(!first&&dp[pos][sta]!=-1)return dp[pos][sta];
    int up=first?num[pos]:9;
    ll ans=0;
    for(int i=0;i<=up;i++){
        if(pre==4&&i==9)continue;
        ans+=dfs(pos-1,i,i==4,first&&i==num[pos]);
    }
    if(!first)dp[pos][sta]=ans;
    return ans;
}
ll ac(ll x){
    int pos=0;
    while(x){
        num[pos++]=x%10;
        x/=10;
    }
    return dfs(pos-1,-1,0,true);
}
int main()
{
    std::ios::sync_with_stdio(false);
    std::cin.tie(0);
    std::cout.tie(0);
    ll n;
    memset(dp,-1,sizeof(dp));
    int t;
    cin>>t;
    while(t--){
        cin>>n;
        cout<<n-ac(n)+1<<endl;
    }
    return 0;
}

思路2

正着做 DP[第几个数]//[这个数这个位是不是4]///[这个数前面存不存在49]

#include<algorithm>
#include <iostream>
#include <stdlib.h>
#include <string.h>
#include  <stdio.h>
#include   <math.h>
#include   <time.h>
#include   <vector>
#include   <bitset>
#include    <queue>
#include      <map>
#include      <set>
using namespace std;
typedef long long ll;
#define pp pair<int,int>
const ll mod=998244353;
const int maxn=1e6+50;
const ll inf=0x3f3f3f3f3f3f3f3fLL;
int gcd(int a,int b){while(b){int t=a%b;a=b;b=t;}return a;}
int num[200];
ll dp[200][2][2];
ll dfs(int pos,int pre,int sta,bool first,bool ok){
    if(pos==-1){
        if(ok)return 1;
        else
            return 0;
    }
    if(!first&&dp[pos][sta][ok]!=-1)return dp[pos][sta][ok];
    int up=first?num[pos]:9;
    ll ans=0;
    for(int i=0;i<=up;i++){
        ans+=dfs(pos-1,i,i==4,first&&i==num[pos],pre==4&&i==9||ok);
    }
    if(!first)dp[pos][sta][ok]=ans;
    return ans;
}
ll ac(ll x){
    ll pos=0;
    while(x){
        num[pos++]=x%10;
        x/=10;
    }
    return dfs(pos-1,-1,0,true,false);
}
int main()
{
    std::ios::sync_with_stdio(false);
    std::cin.tie(0);
    std::cout.tie(0);
    memset(dp,-1,sizeof(dp));
    int t;
    cin>>t;
    while(t--){
        ll n;
        cin>>n;
        cout<<ac(n)<<endl;
    }
    return 0;
}

E.POJ - 3252 Round Numbers

题意

求L到R中二进制里面0的数量大于等于1的数量的个数

思路

二进制的数位DP,但是注意这个有关前导0,如果有前导零的话遇到0就不能加上0的数量;

dp【第几位】【0的数量】【1的数量】

#include<algorithm>
#include <iostream>
#include <stdlib.h>
#include <string.h>
#include  <stdio.h>
#include   <math.h>
#include   <time.h>
#include   <vector>
#include   <bitset>
#include    <queue>
#include      <map>
#include      <set>
using namespace std;
typedef long long ll;
#define pp pair<int,int>
const ll mod=998244353;
const int maxn=1e6;
const ll inf=0x3f3f3f3f3f3f3f3fLL;
int gcd(int a,int b){while(b){int t=a%b;a=b;b=t;}return a;}
int lcm(int a,int b){return a*b/gcd(a,b);}
int num[200];
ll dp[200][200][200];
ll dfs(int pos,int num0,int num1,bool limit,bool zero){
    if(pos==-1)return num0>=num1;
    if(!limit&&dp[pos][num0][num1]!=-1)return dp[pos][num0][num1];
    int up=limit?num[pos]:1;
    ll ans=0;
    for(int i=0;i<=up;i++){
        if(i)ans+=dfs(pos-1,num0,num1+1,limit&&i==num[pos],zero&&i==0);
        else ans+=dfs(pos-1,num0+(zero?0:1),num1,limit&&i==num[pos],zero&&i==0);
    }
    if(!limit)dp[pos][num0][num1]=ans;
    return ans;
}
ll ac(ll x){
    ll pos=0;
    while(x){
        num[pos++]=x&1;
        x/=2;
    }
    return dfs(pos-1,0,0,true,true);
}
int main()
{
    std::ios::sync_with_stdio(false);
    std::cin.tie(0);
    std::cout.tie(0);
    memset(dp,-1,sizeof(dp));
    ll n,m;
    cin>>n>>m;
    cout<<ac(m)-ac(n-1)<<endl;
    return 0;
}

F.HDU - 3709 Balanced Number

题意

求范围内的平衡数字的个数,从数字到枢轴的距离是它与枢轴之间的偏移。然后可以计算左右部分的扭矩。如果它们是相同的,它是平衡的。平衡数字必须与其某些数字处的枢轴平衡。例如,4139是一个平衡数字,其中枢轴固定为3.对于左侧部分和右侧部分,扭矩分别为4 * 2 + 1 * 1 = 9和9 * 1 = 9。

思路

枚举枢轴的位置(1-len)然后和加起来,易知一个数最多只能有一个枢轴,假设有一个枢轴 后移动枢轴位置的,那么 平衡数字不可能会变成0.

然后再减去前导0的数比如00 000 0000

如果中间的平衡数的值小于0了那么这个数的这个位置为枢轴的肯定没有平衡数;比如前面已经4359以3为枢轴 左边是4*1 右边是5 ->4-5=-1 在过去值肯定比-1要小

#include<algorithm>
#include <iostream>
#include <stdlib.h>
#include <string.h>
#include  <stdio.h>
#include   <math.h>
#include   <time.h>
#include   <vector>
#include   <bitset>
#include    <queue>
#include      <map>
#include      <set>
using namespace std;
typedef long long ll;
#define pp pair<int,int>
const ll mod=998244353;
const int maxn=1e6;
const ll inf=0x3f3f3f3f3f3f3f3fLL;
int gcd(int a,int b){while(b){int t=a%b;a=b;b=t;}return a;}
int lcm(int a,int b){return a*b/gcd(a,b);}
int num[21];
ll dp[21][21][2200];
ll dfs(int pos,int on,int nownum,bool limit){
    if(pos==-1)return nownum==0;
    if(nownum<0)return 0;
    if(!limit&&dp[pos][on][nownum]!=-1)return dp[pos][on][nownum];
    int up=limit?num[pos]:9;
    ll ans=0;
    for(int i=0;i<=up;i++){
        ans+=dfs(pos-1,on,nownum+(pos+1-on)*i,limit&&i==num[pos]);
    }
    if(!limit)dp[pos][on][nownum]=ans;
    return ans;
}
ll ac(ll x){
    if(x<0)return 0;
    ll pos=0;
    while(x){
        num[pos++]=x%10;
        x/=10;
    }
    ll ans=0;
    for(int i=1;i<=pos;i++)ans+=dfs(pos-1,i,0,1);
    return ans-(pos-1);
}
int main()
{
    std::ios::sync_with_stdio(false);
    std::cin.tie(0);
    std::cout.tie(0);
    memset(dp,-1,sizeof(dp));
    int t;
    cin>>t;
    ll a,b;
    int cnt=0;
    while(t--){
        cin>>a>>b;
        cout<<ac(b)-ac(a-1)<<endl;
    }
    return 0;
}

G.HDU - 3652 B-number

题意

要13并且能被13整除的数;模板题,要标记当前余的数和前面的那个数!!和是否有13

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
#define pp pair<int,int>
const ll mod=998244353;
const int maxn=1e6+50;
const ll inf=0x3f3f3f3f3f3f3f3fLL;
int gcd(int a,int b){while(b){int t=a%b;a=b;b=t;}return a;}
int lcm(int a,int b){return a*b/gcd(a,b);}
int num[11];
int dp[11][15][2][15];
int dfs(int pos,int pre,bool flag,int nownum,bool limit){
    if(pos==-1)return (flag)&&(nownum==0);
    if(dp[pos][nownum][flag][pre]!=-1&&!limit)return dp[pos][nownum][flag][pre];
    int up=limit?num[pos]:9;
    int ans=0;
    for(int i=0;i<=up;i++){
        ans+=dfs(pos-1,i,flag||(pre==1&&i==3),(nownum*10+i)%13,limit&&(i==num[pos]));
    }
    if(!limit)dp[pos][nownum][flag][pre]=ans;
    return ans;
}
int ac(int x){
    int ans=0;
    while(x){
        num[ans++]=x%10;
        x/=10;
    }
    return dfs(ans-1,0,false,0,true);
}
int main()
{
    std::ios::sync_with_stdio(false);
    std::cin.tie(0);
    std::cout.tie(0);
    int n;
    memset(dp,-1,sizeof(dp));
    while(cin>>n){
        cout<<ac(n)<<endl;
    }
    return 0;
}

H.HDU - 4734 F(x)

题意

一个A 一个B,给出一个F(x)的定义,让你求出0-B中有多少个F(x)<=F(a)

思路

一开始是想直接存每个数的F(x)然后和F(a)比较,不过这样就不能记忆化搜索了,后来发现直接存F(a)-F(x)的差值就行了如果差值小于0就直接返回 ,而且F(a)的最大值只有20736左右

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
#define pp pair<int,int>
const ll mod=998244353;
const int maxn=1e6+50;
const ll inf=0x3f3f3f3f3f3f3f3fLL;
int gcd(int a,int b){while(b){int t=a%b;a=b;b=t;}return a;}
int lcm(int a,int b){return a*b/gcd(a,b);}
int num[11];
int dp[11][20736];
int dfs(int pos,bool limit,int sum){
    if(pos==-1)return sum>=0;
    if(sum<0)return 0;
    if(dp[pos][sum]!=-1&&!limit)return dp[pos][sum];
    int up=limit?num[pos]:9;
    int ans=0;
    for(int i=0;i<=up;i++){
        ans+=dfs(pos-1,limit&&(i==num[pos]),sum-i*(1<<pos));
    }
    if(!limit)dp[pos][sum]=ans;
    return ans;
}
int ac(int x,int sum){
    int ans=0;
    while(x){
        num[ans++]=x%10;
        x/=10;
    }
    return dfs(ans-1,true,sum);
}
int getA(int x){
    int ans=0;
    while(x){
        num[ans++]=x%10;
        x/=10;
    }
    int sum=0;
    for(int i=0;i<ans;i++){
        sum+=(1<<i)*num[i];
    }
    return sum;
}
int main()
{
    std::ios::sync_with_stdio(false);
    std::cin.tie(0);
    std::cout.tie(0);
    memset(dp,-1,sizeof(dp));
    int t;
    cin>>t;
    int cnt=0;
    while(t--){
        int a,b;
        cin>>a>>b;
        int sum=getA(a);
        cout<<"Case #"<<++cnt<<": ";
        cout<<ac(b,sum)<<endl;
    }
    return 0;
}

I.ZOJ - 3494 BCD Code

题意

给定N个01串,再给定区间[a,b],问区间[a,b]里面有多少个数转化成BCD码之后不包含任何前面给出01串。1 <= A <= B <= 10^200

题解

正好最近都在做AC自动机,一看到不包括前面的01串马上想到自动机....首先把前面的N个01串扔进AC自动机里面求出坏点,表示当前当前点可以走那些数字,然后判断0-9的BCD码有没有接触到不能走的点

注意数字太大要用字符串存取

这题真有趣,真好AC自动机和数位DP的结合

#include<algorithm>
#include <iostream>
#include <stdlib.h>
#include <string.h>
#include  <stdio.h>
#include   <math.h>
#include   <time.h>
#include   <vector>
#include   <bitset>
#include    <queue>
#include      <map>
#include      <set>
using namespace std;
typedef long long ll;
#define pp pair<int,int>
const ll mod=1e9+9;
const int maxn=1500;
const ll inf=0x3f3f3f3f3f3f3f3fLL;
int gcd(int a,int b){while(b){int t=a%b;a=b;b=t;}return a;}
struct Trie{
    int next[2010][2],fail[2010];bool end[2010];
    int L,root;
    int newnode(){
        for(int i=0;i<2;i++){
            next[L][i]=-1;
        }
        end[L++]=false;
        return L-1;
    }
    void init(){
        L=0;
        root=newnode();
    }
    void insert(char buf[]){
        int len=strlen(buf);
        int now=root;
        for(int i=0;i<len;i++){
            if(next[now][buf[i]-'0']==-1)
                next[now][buf[i]-'0']=newnode();
            now=next[now][buf[i]-'0'];
        }
        end[now]=true;
    }
    void build(){
        queue<int>Q;
        fail[root]=root;
        for(int i=0;i<2;i++)
            if(next[root][i]==-1)next[root][i]=root;
            else{
                fail[next[root][i]]=root;
                Q.push(next[root][i]);
            }
        while(!Q.empty()){
            int now=Q.front();
            Q.pop();
            if(end[fail[now]]==true)end[now]=true;
            for(int i=0;i<2;i++)
                if(next[now][i]==-1)next[now][i]=next[fail[now]][i];
                else{
                    fail[next[now][i]]=next[fail[now]][i];
                    Q.push(next[now][i]);
                }
        }

    }
};
Trie ac;
int num[250];
ll dp[250][2005];
ll dfs(int pos,int sta,bool limit,bool zero){
    if(pos==-1)return !zero;
    if(!zero&&!limit&&dp[pos][sta]!=-1)return dp[pos][sta];
    int up=limit?num[pos]:9;
    ll ans=0;
    for(int i=0;i<=up;i++){
        if(zero&&i==0)ans=(ans+dfs(pos-1,sta,limit&&i==up,zero&&i==0)+mod)%mod;
        else{
            int nex=sta,x=i;
            bool flag=true;
            for(int j=(1<<3);j;j>>=1){
                int bit=x/j;x=x-bit*j;
                nex=ac.next[nex][bit];
                if(ac.end[nex]){
                    flag=false;break;
                }
            }
            if(flag)ans=(ans+dfs(pos-1,nex,limit&&i==up,zero&&i==0)+mod)%mod;
        }
    }
    if(!zero&&!limit)dp[pos][sta]=ans;
    return ans;
}
ll acc(char x[]){
    int len=strlen(x);
    for(int i=0;i<len;i++)num[i]=x[len-1-i]-'0';
    return dfs(len-1,0,true,true);
}
char str[220];
char st[220],ed[220];
int main()
{
    std::ios::sync_with_stdio(false);
    std::cin.tie(0);
    std::cout.tie(0);
    int t;
    cin>>t;
    while(t--){
        int n;
        cin>>n;
        ac.init();
        memset(dp,-1,sizeof(dp));
        for(int i=0;i<n;i++){
            cin>>str;
            ac.insert(str);
        }
        ac.build();
        cin>>st>>ed;
        int lena=strlen(st),p=lena-1;
        while(st[p]=='0')st[p--]='9';
        st[p]=st[p]-1;
        cout<<((acc(ed)-acc(st)+mod)%mod)<<endl;
    }
    return 0;
}

J.HDU - 4507 吉哥系列故事――恨7不成妻

题意

求指定范围内与7不沾边的所有数的平方和。结果要mod 10^9+7

思路

与7不沾边的数需要满足三个条件。

①不出现7

②各位数和不是7的倍数

③这个数不是7的倍数

这三个条件都是基础的数位DP。

但是这题要统计的不是符合条件个数,而是平方和。

也就是说在DP时候,要重建每个数,算出平方,然后求和。

需要维护三个值(推荐使用结构体), 假定dfs推出返回的结构体是next,当前结果的结构体是ans

①符合条件数的个数 cnt

②符合条件数的和 sum

③符合添加数的平方和 sqsum

其中①是基础数位DP。②next.sum+(10^len×i)×ans.cnt,其中(10^len×i)×ans.cnt代表以len为首位的这部分数字和。

③首先重建一下这个数,(10^len×i+x),其中x是这个数的后面部分,则平方和就是(10^len×i)^2+x^2+2×10^len×i×x,其中x^2=next.sqsum

整体还要乘以next.cnt,毕竟不止一个。

这样sqsum+=next.sqsum

sqsum+=(2×10^len×i×x)×next.cnt=(2×10^len×i)×next.sum(神奇的化简)

sqsum+=(10^len×i)^2×next.cnt

mod之后统计函数也有个小陷阱,那就是f(r)在mod之后有可能小于f(l-1)。也就是要对负数取正数模。

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
#define pp pair<int,int>
const ll mod=1e9+7;
const int maxn=1e6+50;
const ll inf=0x3f3f3f3f3f3f3f3fLL;
int gcd(int a,int b){while(b){int t=a%b;a=b;b=t;}return a;}
int lcm(int a,int b){return a*b/gcd(a,b);}
int num[20];
ll ten[20];
struct node{
    ll cnt,sqr,sum;
    bool vis;
    node(ll a=0,ll b=0,ll c=0,bool d=0):cnt(a),sqr(b),sum(c),vis(d){}
}dp[20][11][11];
node dfs(int pos,ll mod1,ll mod2,bool limit){
    if(pos==-1){
        if(mod1%7==0||mod2%7==0)return node(0);
        return node(1);
    }
    if(dp[pos][mod1][mod2].vis&&!limit)return dp[pos][mod1][mod2];
    int up=limit?num[pos]:9;
    node now;
    for(int i=0;i<=up;i++){
        if(i==7)continue;
        node tmp=dfs(pos-1,(mod1+i)%7,(mod2*10+i)%7,limit&&i==num[pos]);
        ll aa=i*ten[pos]%mod;
        now.cnt=(now.cnt+tmp.cnt)%mod;
        now.sum=(now.sum+aa*tmp.cnt%mod+tmp.sum)%mod;
        now.sqr=(aa*aa%mod*tmp.cnt%mod+2*aa*tmp.sum%mod+tmp.sqr+now.sqr)%mod;
    }
    if(!limit){
        dp[pos][mod1][mod2]=now;
        dp[pos][mod1][mod2].vis=1;
    }
    return now;
}
ll ac(ll x){
    int ans=0;
    while(x){
        num[ans++]=x%10;
        x/=10;
    }
    node ret=dfs(ans-1,0,0,1);
    return ret.sqr%mod;
}
int main()
{
    std::ios::sync_with_stdio(false);
    std::cin.tie(0);
    std::cout.tie(0);
    int t;
    cin>>t;
    ten[0]=1;for(int i=1;i<=18;i++)ten[i]=ten[i-1]*10;
    while(t--){
        ll a,b;
        cin>>a>>b;
        cout<<(ac(b)-ac(a-1)+mod)%mod<<endl;
    }
    return 0;
}

K.SPOJ - BALNUM Balanced Numbers

题意

问[L, R]内有多少数字,满足每个奇数都出现了偶数次,每个偶数都出现了奇数次(没有出现的数不考虑)

题解1

用三进制来表示状态,0表示没出现,1表示出现奇数次,2表示出现偶数次。

然后就是裸的数位DP了

特别注意&&的优先度是低于==的

#include<algorithm>
#include <iostream>
#include <stdlib.h>
#include <string.h>
#include  <stdio.h>
#include   <math.h>
#include   <time.h>
#include   <vector>
#include   <bitset>
#include    <queue>
#include      <map>
#include      <set>
using namespace std;
typedef long long ll;
#define pp pair<int,int>
const ll mod=998244353;
const int maxn=1e5+50;
const ll inf=0x3f3f3f3f3f3f3f3fLL;
int gcd(int a,int b){while(b){int t=a%b;a=b;b=t;}return a;}
int num[25];
int three[10];
ll dp[25][maxn];
int isok(int sta){
    for(int i=0;i<=9;i++,sta/=3){
        int k=sta%3;
        if(!k)continue;
        if((i&1)&&(k&1))return 0;
        if(((i&1)==0)&&(k==2))return 0;
    }
    return 1;
}

int change(int sta,int i){
    int nu[10];
    for(int j=0;j<=9;j++,sta/=3)nu[j]=sta%3;
    if(nu[i]==0)nu[i]=1;
    else nu[i]=3-nu[i];
    for(int j=9;j>=0;j--)sta=sta*3+nu[j];
    return sta;
}
ll dfs(int pos,int sta,bool first,bool zero){
    if(pos==-1)return isok(sta);
    if(!first&&dp[pos][sta]!=-1)return dp[pos][sta];
    int up=first?num[pos]:9;
    ll ans=0;
    for(int i=0;i<=up;i++){
        if(zero&&i==0)
            ans+=dfs(pos-1,sta,first&&i==num[pos],zero&&i==0);
        else
            ans+=dfs(pos-1,change(sta,i),first&&i==num[pos],zero&&i==0);
    }
    if(!first)dp[pos][sta]=ans;
    return ans;
}
ll ac(ll x){
    int pos=0;
    while(x){
        num[pos++]=x%10;
        x/=10;
    }
    return dfs(pos-1,0,true,true);
}
int main()
{
    std::ios::sync_with_stdio(false);
    std::cin.tie(0);
    std::cout.tie(0);
    memset(dp,-1,sizeof(dp));
    int t;
    cin>>t;
    while(t--){
        ll m,n;
        cin>>n>>m;
        cout<<ac(m)-ac(n-1)<<endl;
    }
    return 0;
}

解法2

可以多出一阶存1-9是否出现过 不过数组不能开到1<<11 要开到1<<10+50左右不然会超时

#include<algorithm>
#include <iostream>
#include <stdlib.h>
#include <string.h>
#include  <stdio.h>
#include   <math.h>
#include   <time.h>
#include   <vector>
#include   <bitset>
#include    <queue>
#include      <map>
#include      <set>
using namespace std;
typedef long long ll;
#define pp pair<int,int>
const ll mod=998244353;
const int maxn=1024+5;
const ll inf=0x3f3f3f3f3f3f3f3fLL;
int gcd(int a,int b){while(b){int t=a%b;a=b;b=t;}return a;}
int num[25];
ll dp[25][maxn][maxn];
int isok(int sta,int haha){
    for(int i=0;i<=9;i++){
        if((haha&(1<<i))==0)continue;
        if(i&1){//奇数出现偶数次
            if(sta&(1<<i)){
                return 0;
            }
        }
        else{
            if((sta&(1<<i))==0){
                return 0;
            }
        }
    }
    return 1;
}
int change(int sta,int i){
    return sta^(1<<i);
}
ll dfs(int pos,int sta,int haha,bool first,bool zero){
    if(pos==-1)return isok(sta,haha);
    if(!first&&dp[pos][sta][haha]!=-1)return dp[pos][sta][haha];
    int up=first?num[pos]:9;
    ll ans=0;
    for(int i=0;i<=up;i++){
        ll now=change(sta,i);
        if(zero&&i==0)
            ans+=dfs(pos-1,sta,haha,first&&i==num[pos],zero&&i==0);
        else
            ans+=dfs(pos-1,change(sta,i),haha|(1<<i),first&&i==num[pos],zero&&i==0);
    }
    if(!first)dp[pos][sta][haha]=ans;
    return ans;
}
ll ac(ll x){
    int pos=0;
    while(x){
        num[pos++]=x%10;
        x/=10;
    }
    return dfs(pos-1,0,0,true,true);
}
int main()
{
    std::ios::sync_with_stdio(false);
    std::cin.tie(0);
    std::cout.tie(0);
    memset(dp,-1,sizeof(dp));
    int t;
    cin>>t;
    while(t--){
        ll m,n;
        cin>>n>>m;
        cout<<ac(m)-ac(n-1)<<endl;
    }
    return 0;
}

以上是关于「kuangbin带你飞」专题十五 数位DP的主要内容,如果未能解决你的问题,请参考以下文章

「kuangbin带你飞」专题二十 斜率DP

「kuangbin带你飞」专题十二 基础DP

「kuangbin带你飞」专题二十二 区间DP

[kuangbin带你飞]专题二十二 区间DP----POJ - 2955

算法系列学习[kuangbin带你飞]专题十二 基础DP1 G - 免费馅饼

算法系列学习状压dp [kuangbin带你飞]专题十二 基础DP1 D - Doing Homework