数位DP

蓝咒 提交于 2020-03-28 20:40:25

这篇博客主要是数位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

 

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!