• 浅(口)谈(胡)数位$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);
        }
    }
  • 相关阅读:
    项目中的注意事项
    复合查询
    树型控件的处理(完整版)
    图的存储结构(邻接矩阵)
    图的定义与术语2 数据结构和算法55
    图的存储结构(邻接矩阵)
    赫夫曼编码 数据结构和算法52
    赫夫曼编码 数据结构和算法52
    图的存储结构(邻接矩阵)
    图的定义与术语 数据结构和算法54
  • 原文地址:https://www.cnblogs.com/wo-shi-zhen-de-cai/p/10380525.html
Copyright © 2020-2023  润新知