zoukankan      html  css  js  c++  java
  • 数位DP

    这篇博客主要是数位DP的模版

    基本上数位DP的题目都是比较套路的:一般都是问一个区间中满足条件的数的个数

    套路1:差分

    • [L,R]中的答案 = [0,R]中答案 - [0,L]中答案 + chk(L)

    套路2:按位DP(记忆化搜索实现),记录需要的前缀状态,特别的:

    • f = 0/1 代表当前填的数字是否还和上界数字相同
    • g = 0/1 代表当前是否还在填前导零

    复杂度一般为 O(位数*前缀状态数*10)

    【P2657 [SCOI2009]windy数】https://www.luogu.com.cn/problem/P2657

    不含前导零且相邻两个数字之差至少为2的正整数被称为windy数。 windy想知道,在A和B之间,包括A和B,总共有多少个windy数?

    #pragma GCC optimize(3,"Ofast","inline")//O3优化
    #pragma GCC optimize(2)//O2优化
    #include <algorithm>
    #include <string>
    #include <string.h>
    #include <vector>
    #include <map>
    #include <stack>
    #include <set>
    #include <queue>
    #include <math.h>
    #include <cstdio>
    #include <iomanip>
    #include <time.h>
    #include <bitset>
    #include <cmath>
    #include <sstream>
    #include <iostream>
    #include <cstring>
    
    #define LL long long
    #define ls nod<<1
    #define rs (nod<<1)+1
    #define pii pair<int,int>
    #define mp make_pair
    #define pb push_back
    #define INF 0x3f3f3f3f
    #define max(a,b) (a>b?a:b)
    #define min(a,b) (a<b?a:b)
    
    const double eps = 1e-10;
    const int maxn = 2e5 + 10;
    const int mod = 1e9 + 7;
    
    int sgn(double a){return a < -eps ? -1 : a < eps ? 0 : 1;}
    using namespace std;
    
    int mem[100][100][2][2];
    int len;
    string L,R;
    string s;
    
    int dfs(int cur,int x,bool f,bool g) {
        if (cur == len)
            return 1;
        if (mem[cur][x][f][g] != -1)
            return mem[cur][x][f][g];
        int ans = 0;
        int v = 9;
        if (f == 1)
            v = s[cur] - '0';
        for (int i = 0;i <= v;i++) {
            if (g == 1) {
                if (i == 0)
                    ans += dfs(cur+1,0,f&(i==v),1);
                else
                    ans += dfs(cur+1,i,f&(i==v),0);
            }
            else if (abs(i-x) >= 2)
                ans += dfs(cur+1,i,f&(i==v),0);
        }
        return mem[cur][x][f][g] = ans;
    }
    
    int solve(string t) {
        s = t;
        len = s.length();
        memset(mem,-1, sizeof(mem));
        return dfs(0,0,1,1);
    }
    
    int check(string t) {
        for (int i =  1;i < t.length();i++) {
            if (abs(t[i]-t[i-1]) < 2)
                return 0;
        }
        return 1;
    }
    
    int main() {
        ios::sync_with_stdio(0);
        cin >> L >> R;
        LL ans = solve(R) - solve(L) + check(L);
        cout << ans << endl;
        return 0;
    }

    【AcWing 310. 启示录】https://www.acwing.com/problem/content/312/

    某数字的十进制表示中有三个连续的6,古代人也认为这是个魔鬼的数。求第k小的魔鬼数

    只需要在之前那个模版上改变一下,记录一下前两位数以及该数是不是魔鬼数

    然后我们进行二分,找到第一个数 R 内魔鬼数的个数 <= k-1

    那么这个 R 就是符合题目要求的

    #pragma GCC optimize(3,"Ofast","inline")//O3优化
    #pragma GCC optimize(2)//O2优化
    #include <algorithm>
    #include <string>
    #include <string.h>
    #include <vector>
    #include <map>
    #include <stack>
    #include <set>
    #include <queue>
    #include <math.h>
    #include <cstdio>
    #include <iomanip>
    #include <time.h>
    #include <bitset>
    #include <cmath>
    #include <sstream>
    #include <iostream>
    #include <cstring>
    
    #define LL long long
    #define ls nod<<1
    #define rs (nod<<1)+1
    #define pii pair<int,int>
    #define mp make_pair
    #define pb push_back
    #define INF 0x3f3f3f3f
    #define max(a,b) (a>b?a:b)
    #define min(a,b) (a<b?a:b)
    
    const double eps = 1e-10;
    const int maxn = 2e5 + 10;
    const int mod = 1e9 + 7;
    
    int sgn(double a){return a < -eps ? -1 : a < eps ? 0 : 1;}
    using namespace std;
    
    int mem[11][2][2][2][2];
    int len,k;
    int b[11];
    
    int dfs(int cur,bool p1,bool p2,bool m,bool f) {
        if (cur == len)
            return m == 1;
        if (mem[cur][p1][p2][m][f] != -1)
            return mem[cur][p1][p2][m][f];
        int v = 9;
        if (f)
            v = b[cur];
        LL ans = 0;
        for (int i = 0;i <= v;i++) {
            ans += dfs(cur+1,i == 6,p1,m || (p1 && p2 && (i == 6)),f && (i == v));
        }
        return mem[cur][p1][p2][m][f] = ans;
    }
    
    
    bool check(LL x) {
        len = 0;
        while (x) {
            b[len++] = x % 10;
            x /= 10;
        }
        reverse(b,b+len);
        memset(mem,-1, sizeof(mem));
        return dfs(0,0,0,0,1) <= k;
    }
    
    
    
    int main() {
        ios::sync_with_stdio(0);
        int T;
        cin >> T;
        while (T--) {
            cin >> k;
            k--;
            LL ans = INF;
            LL l = 0,r = 1e10;
            while (l < r) {
                LL mid = (l + r)/2;
                if (check(mid))
                    l = mid + 1;
                else {
                    r = mid;
                }
            }
            cout << r << endl;
        }
        return 0;
    }

    【AcWing 311. 月之谜】https://www.acwing.com/problem/content/313/

    如果一个十进制数能够被它的各位数字之和整除,则称这个数为“月之数”。给定整数L和R,你需要计算闭区间[L,R]中有多少个“月之数”。

    转换一下思路,我们去枚举这个十进制数的各数字的和这样就可以减少复杂度

    #pragma GCC optimize(3,"Ofast","inline")//O3优化
    #pragma GCC optimize(2)//O2优化
    #include <algorithm>
    #include <string>
    #include <string.h>
    #include <vector>
    #include <map>
    #include <stack>
    #include <set>
    #include <queue>
    #include <math.h>
    #include <cstdio>
    #include <iomanip>
    #include <time.h>
    #include <bitset>
    #include <cmath>
    #include <sstream>
    #include <iostream>
    #include <cstring>
    
    #define LL long long
    #define ls nod<<1
    #define rs (nod<<1)+1
    #define pii pair<int,int>
    #define mp make_pair
    #define pb push_back
    #define INF 0x3f3f3f3f
    #define max(a,b) (a>b?a:b)
    #define min(a,b) (a<b?a:b)
    
    const double eps = 1e-10;
    const int maxn = 2e5 + 10;
    const int mod = 1e9 + 7;
    
    int sgn(double a){return a < -eps ? -1 : a < eps ? 0 : 1;}
    using namespace std;
    
    string L,R;
    int sum,len;
    string s;
    int mem[10][110][110][2];
    
    int dfs(int cur,int sum1,int r,bool f) {
        if (cur == len)
            return sum1 == sum && r == 0;
        if (mem[cur][sum1][r][f] != -1)
            return mem[cur][sum1][r][f];
        int v = 9;
        if (f)
            v = s[cur] - '0';
        LL ans = 0;
        for (int i = 0;i <= v;i++) {
            ans += dfs(cur+1,sum1+i,(r*10+i)%sum,f && (i == v));
        }
        return mem[cur][sum1][r][f] = ans;
    }
    
    int solve(string t) {
        s = t;
        len = s.length();
        memset(mem,-1, sizeof(mem));
        return dfs(0,0,0,1);
    }
    
    int check(string t) {
        len = t.length();
        int sum1 = 0,x = 0;
        for (int i = 0;i < len;i++) {
            sum1 += (t[i] - '0');
            x = x * 10 + (t[i]-'0');
        }
        return sum == sum1 && x % sum == 0;
    }
    int main() {
        ios::sync_with_stdio(0);
        cin >> L >> R;
        LL ans = 0;
        for (sum = 1;sum <= 100;sum++) {
            ans += solve(R)-solve(L)+check(L);
        }
        cout << ans << endl;
        return 0;
    }

    【AcWing 338. 计数问题】https://www.acwing.com/problem/content/340/

    给定两个整数 a 和 b,求 a 和 b 之间的所有数字中0~9的出现次数。

    对每个数位 0~9 我们都进行数位DP

    #pragma GCC optimize(3,"Ofast","inline")//O3优化
    #pragma GCC optimize(2)//O2优化
    #include <algorithm>
    #include <string>
    #include <string.h>
    #include <vector>
    #include <map>
    #include <stack>
    #include <set>
    #include <queue>
    #include <math.h>
    #include <cstdio>
    #include <iomanip>
    #include <time.h>
    #include <bitset>
    #include <cmath>
    #include <sstream>
    #include <iostream>
    #include <cstring>
    
    #define LL long long
    #define ls nod<<1
    #define rs (nod<<1)+1
    #define pii pair<int,int>
    #define mp make_pair
    #define pb push_back
    #define INF 0x3f3f3f3f
    #define max(a,b) (a>b?a:b)
    #define min(a,b) (a<b?a:b)
    
    const double eps = 1e-10;
    const int maxn = 2e5 + 10;
    const int mod = 1e9 + 7;
    
    int sgn(double a){return a < -eps ? -1 : a < eps ? 0 : 1;}
    using namespace std;
    
    string L,R;
    string s;
    int k,len;
    int mem[11][11][2][2];
    
    int dfs(int cur,int cnt,bool g,bool f) {
        if (cur == len)
            return cnt;
        if (mem[cur][cnt][g][f] != -1)
            return mem[cur][cnt][g][f];
        int v = 9;
        if (f)
            v = s[cur] - '0';
        LL ans = 0;
        for (int i = 0;i <= v;i++) {
            if (g == 1) {
                if (i == 0)
                    ans += dfs(cur+1,0,1,f && (i == v));
                else
                    ans += dfs(cur+1,cnt+(i==k),0,f && (i == v));
            }
            else
                ans += dfs(cur+1,cnt+(i==k),0,f && (i == v));
        }
        return mem[cur][cnt][g][f] = ans;
    }
    
    int solve(string t) {
        s = t;
        len = s.length();
        memset(mem,-1, sizeof(mem));
        return dfs(0,0,1,1);
    }
    
    int check(string t) {
        len = t.length();
        int cnt = 0;
        for (int i = 0;i < len;i++) {
            if (t[i]-'0'==k)
                cnt++;
        }
        return cnt;
    }
    int main() {
        ios::sync_with_stdio(0);
        while (1) {
            cin >> L >> R;
            if (L == "0" && R == "0")
                break;
            if(R.length()<L.length() || R.length()==L.length() && R<L) swap(L,R);
            LL ans = 0;
            for (k = 0; k <= 9; k++) {
                ans = solve(R) - solve(L) + check(L);
                cout << ans << " ";
            }
            cout << endl;
        }
        return 0;
    }

    博客参考:https://www.luogu.com.cn/blog/BeWild/post-shuo-wei-dp

  • 相关阅读:
    前世今生:Hive、Shark、spark SQL
    spark streaming 6: BlockGenerator、RateLimiter
    spark streaming 5: InputDStream
    spark streaming 4: DStreamGraph JobScheduler
    常见css水平自适应布局
    js动态加载以及确定加载完成的代码
    如何判断css是否加载完成
    翻书特效
    jquery 事件冒泡的介绍以及如何阻止事件冒泡
    phonegap之android原生日历调用
  • 原文地址:https://www.cnblogs.com/-Ackerman/p/12589079.html
Copyright © 2011-2022 走看看