浅(口)谈(胡)数位$DP$

作为DP界的又一股清流,数位DP有着自己独特的思考方式。

一般来说,数位DP可以解决类似的问题

  • 中符合某种特定要求的数的个数

按照一般DP的思想,我们会认为应该这么设:表示1~​i中间符合要求的数的个数

但是通常来看,对于一般的数位DP这样并不能列方程,数位DP的要求一般和数位有莫大的关系,而从这个状态中我们可以发现这和数位并没有太大关系,因此不太好写方程。

那么我么就可以考虑一下其他的状态,下面我们可以结合一道题来看一下:

Windy数

题意:求在中不含前导零且相邻两个数的差至少为2的正整数的个数。

先来考虑暴力,显然有:

for(int i=l;i<=r;i++)
    if(check(i)) ans++;

但显然这种暴力一定会TLE,并且我们没有充分利用题目要求的优越性。

考虑记搜,我们可以设dp[i][j]表示搜到第​i位,上一位数j为的方案总数。

然后考虑如何控制边界,比如:

2541654​为最大值,我们当前搜到​2541***时,显然下一位不能超过6。

所以我们应该将边界值拆开成一位一位的数,这样比较好算。

但是我们发现如果要处理最小值时,比较不好处理,因此我们可以利用前缀和的思想来求解,求出~~的答案即可。

对于每一位枚举时,要注意我们要从~枚举,因此会有可能有前导零的影响。具体还有一些细节看代码:

#include<bits/stdc++.h>
using namespace std;
inline int read()
{
    int f=1,w=0;char x=0;
    while(x<'0'||x>'9') {if(x=='-') f=-1; x=getchar();}
    while(x!=EOF&&x>='0'&&x<='9') {w=(w<<3)+(w<<1)+(x^48);x=getchar();}
    return w*f;
}
long long l,r,len;
long long dp[21][21],a[21];
long long dfs(int pos,int las,int zer,int hig)
{
    if(pos>len) return 1;
    if(!hig&&dp[pos][las]) return dp[pos][las];//注意这里的高位判定
    long long ans=0;
    int maxn=hig?a[len-pos+1]:9;//能枚举到的最大值,与高位有关
    for(int i=0;i<=maxn;i++)
    {
        if(abs(las-i)<2) continue;
        if(zer&&!i) ans+=dfs(pos+1,-2,1,0);
        else ans+=dfs(pos+1,i,0,hig&&i==maxn);
    }
    if(!hig&&!zer) dp[pos][las]=ans;//注意这里的高位判定
    return ans;
}
inline long long Work(int x)//拆分边界
{
    len=0;
    while(x) a[++len]=x%10,x/=10;
    memset(dp,0,sizeof(dp));
    return dfs(1,-2,1,1);//因为要从零枚举起,所以要令0位上为-2;
}
int main(){
    l=read(),r=read();
    printf("%lld",Work(r)-Work(l-1));
}

因为数位DP不太好,又做了一个题:

$PojApocalypse Someday$

从这一题中,我们可以很显然的看出数位DP的套路性

即设状态时,一般与题中的要求有关,在转移状态时,与当前状态有关的位都一定要加入状态中。

当然,数位DP也要考虑细节,像高位判定这样的细节一定不能出错!

除此之外,这一题的二分也稍稍有些意思,可以好好思考一下。

在每次处理一个询问之后不必将清零,因为当前的对先一个询问没有影响,保留下来可以节省时间!

#include <iostream>
#include <cstdio>
#include <fstream>
#include <algorithm>
#include <cmath>
#include <deque>
#include <vector>
#include <queue>
#include <string>
#include <cstring>
#include <map>
#include <stack>
#include <set>
using namespace std;
#define int long long 
inline int read()
{
    int f=1,w=0;char x=0;
    while(x<'0'||x>'9') {if(x=='-') f=-1; x=getchar();}
    while(x!=EOF&&x>='0'&&x<='9') {w=(w<<3)+(w<<1)+(x^48);x=getchar();}
    return w*f;
}
const int N=12;
int f[N][N][N][2],x[N],cnt;
inline int dfs(int pos,int las1,int las2,int jud,int hig)
{
    if(!pos) return jud;
    if(!hig&&f[pos][las1][las2][jud]) return f[pos][las1][las2][jud];
    int maxn=hig?x[pos]:9,ans=0,p=(las1==6&&las2==6);
    for(int i=0;i<=maxn;i++)
        ans+=dfs(pos-1,i,las1,jud||(p&&i==6),hig&&(i==maxn));
    if(!hig) return f[pos][las1][las2][jud]=ans;
    else return ans;
}
inline int check(int s)
{
    cnt=0;while(s) x[++cnt]=s%10,s/=10;
    return dfs(cnt,0,0,0,1);
}
main(){
    int t=read();
    while(t--)
    {
        int m=read();
        int l=1,r=1e10;
        while(l<r)
        {
            int mid=l+r>>1;
            if(check(mid)>=m) r=mid;
            else l=mid+1;
        }
        printf("%lld
",r);
    }
}
原文地址:https://www.cnblogs.com/wo-shi-zhen-de-cai/p/10380525.html