• 数位DP总结


    数位(DP)

    经过一天半的苦苦挣扎,真的很感谢(Acwing \,\, yxc)的讲解,终于感觉自己入门一些了。

    数位(DP)通常是给定一个([L,R])区间,让你求区间中满足题目要求的数的个数,然后我们只需要利用前缀和思想,分别求出([0, R])([0, L])的满足要求的数的个数,设(f(r))([0, R])的满足要求的数的个数,设(f(l))([0, l])的满足要求的数的个数,然后(f(r) - f(l - 1))即可。

    原来数位(DP)也有模板,模板如下。

    void init() {
    }
    
    int dp(int n) {
    	if (!n) return ?; //通常是特判掉n为0的情况,如果n = 0满足题目要求就返回1,否则返回0
    	
    	vector<int> nums;
    	while (n) nums.push_back(n % 10), n /= 10; //取出所有数位
    	int res = 0, last = 0; //last记录上一位数字是啥玩意
    	
    	for (int i = nums.size() - 1; i >= 0; i--) {
    		int x = nums[i];
    		for (int j = 0; j < x; j++) {
    			if (限制条件) continue;
    			res += f[i + 1][j];
    		}
    		
    		if (限制条件) break;
    		last = x;
    		
    		if (!i && 限制条件满足要求) res++;
    	}
    	
    	return res;
    }
    
    int main() {
    	while (cin >> l >> r, l) {
    		init();
    		
    		cout << dp(r) - dp(l - 1) << endl;	
    	}
    	
        return 0;
    }
    

    还有就是根据题目要求预先处理出(DP)数组,这个(DP)很灵活,可能是就让预处理一个组合数,也可能是一个其他类型的(DP),这也是数位(DP)的难点。

    下面是做的几道题目:

    AcWing 1081. 度的数量

    因为这个题要求填非(0),即(1),我们就需要预处理出一个组合数,然后(last)维护的是前边已经出现几个(1)了, (f[i][j])表示从(i)个数中选(j)个的方案的集合。

    #include <bits/stdc++.h>
    
    using namespace std;
    
    const int N = 35;
    int f[N][N];
    int l, r; 
    int k, b; 
    
    void init() {
        for (int i = 0; i < N; i++) {
            for (int j = 0; j <= i; j++) {
                if (!j) f[i][j] = 1;
                else f[i][j] = f[i - 1][j] + f[i - 1][j - 1];
            }
        }
    }
    
    int dp(int n) {
        if (!n) return 0;
    
        vector<int> nums;
        while (n) nums.push_back(n % b), n /= b;
    
        int res = 0, last = 0;
    
        for (int i = nums.size() - 1; i >= 0; i--) {
            int x = nums[i];
            if (x) {
                res += f[i][k - last]; //如果当前这位为0
                if (x > 1) { //根据上面推出的情况 非1即0,出现x大于1,那么剩下的位数就不用看了。
                    if (k - last - 1 > 0) res += f[i][k - last - 1]; 
                    break;
                } else if (x == 1) {
                    last++;
                    if (last > k) break;
                }
            }
            if (!i && last == k) res++; //最后右边分支的一种,n本身就是一个合法数字
        }
    
        return res;
    }
    
    int main() {
    
        init();
    
        cin >> l >> r;
        cin >> k >> b;
    
        cout << dp(r) - dp(l - 1) << endl;
    
        return 0;
    }
    

    AcWing 1082. 数字游戏

    这个数要求是不降数,那么我们可以把(f[i][j])设成一共有(i)位,且最高位为(j)的不降数的集合,因为要求数字是单调非减的,那么我们(last)维护的就是上一位数字是几,当前数字(x)一定不能小于它。

    #include <bits/stdc++.h>
    
    using namespace std;
    
    const int N = 15;
    
    int f[N][N]; //表示一共有i位,且最高位填j的方案
    
    void init() {
        for (int i = 0; i <= 9; i++) f[1][i] = 1; //一共有1位,先预处理
    
        for (int i = 2; i < N; i++) {
            for (int j = 0; j <= 9; j++) {
                for (int k = j; k <= 9; k++) {
                    f[i][j] += f[i - 1][k];
                }
            }
        }
    }
    
    int dp(int n) {
        if (!n) return 1;
    
        vector<int> nums;
        while (n) nums.push_back(n % 10), n /= 10;
        int res = 0;
        int last = 0;
    
        for (int i = nums.size() - 1; i >= 0; i--) {
            int x = nums[i];
            if (x < last) break;
            for (int j = last; j < x; j++) {
                res += f[i + 1][j];
            }
    
            last = x;
    
            if (!i) res++;
        }
    
        return res;
    }
    
    int main() {
        int l, r;
    
        init();
    
        while (cin >> l >> r) {
            cout << dp(r) - dp(l - 1) << endl;
        }
    
        return 0;
    }
    

    AcWing 1083. Windy数

    要求数位间两个数字之差至少位2,那么(f[i][j])表示为设成一共有(i)位,且最高位为(j)的Windy数的集合,(last)维护的就是上一位数字是几,要求(abs(x - last) >= 2)

    #include <bits/stdc++.h>
    
    using namespace std;
    
    const int N = 15;
    int f[N][N]; //f[i][j]表示有i位,且最高位为j的集合
    
    void init() {
        for (int i = 0; i <= 9; i++) f[1][i] = 1;
    
        for (int i = 2; i < N; i++) {
            for (int j = 0; j <= 9; j++) {
                for (int k = 0; k <= 9; k++) {
                    if (abs(j - k) >= 2) f[i][j] += f[i - 1][k];
                }
            }
        }
    }
    
    int dp(int n) {
        if (!n) return 0;
    
        vector<int> nums;
        while (n) nums.push_back(n % 10), n /= 10;
        int res = 0, last = 12;
        for (int i = nums.size() - 1; i >= 0; i--) {
            int x = nums[i];
            for (int j = i == nums.size() - 1; j < x; j++) {
                if (abs(j - last) >= 2) 
                    res += f[i + 1][j];
            }
    
            if (abs(x - last) >= 2) last = x;
            else break;
    
            if (!i) res++;
        }
    
        //处理含前导0的情况
        for (int i = 1; i < nums.size(); i++) {
            for (int j = 1; j <= 9; j++) {
                res += f[i][j];
            }
        }
    
        return res;
    }
    
    int main() {
        int l, r;
    
        init();
    
        cin >> l >> r;
    
        cout << dp(r) - dp(l - 1) << endl;
    
        return 0;
    }
    

    AcWing 1084. 数字游戏 II

    题目要求数字的各位之和(Mod \,\, P)为0,(f[i][j][k])表示一共有(i)位,且最高位为(j),数位和(Mod \,\,P\,=\,k)的数的集合,(last)表示前边的数位和。

    (nums)里从低到高存了一个数(n)的各位数字,所以倒序枚举,(x)表示当前枚举到的那一位,对于题目的要求需满足,((a_{n - 1} + a_{n - 2} + x + ... +()) \,\, Mod \,\,N = 0),我们用(last)记录前边位数的和,(last = a_{n - 1} + a_{n - 2}),那么再(DP)的过程根据上面的要求,我们需要满足:((x + ... + ()) \,\, Mod \,\, N = -last \,\, Mod \,\, N),注意因为(C++)的特性,负数取模还是负数,所以我们可以这样做,把其取模后的数转成正的。

    int mod(int x, int y) {
    	return (x % y + y) % y;
    }
    
    // Problem: 数字游戏 II
    // Contest: AcWing
    // URL: https://www.acwing.com/problem/content/1086/
    // Memory Limit: 64 MB
    // Time Limit: 1000 ms
    // 
    // Powered by CP Editor (https://cpeditor.org)
    
    #include <bits/stdc++.h>
    
    using namespace std;
    
    const int N = 11, M = 110;
    int f[N][10][M]; //一共有i位且最高位为j的且数位和 Mod P = k的集合
    
    int l, r, P;
    
    int mod(int x, int y) {
    	return (x % y + y) % y;
    }
    
    void init() {
    	memset(f, 0, sizeof f);
    	
    	for (int i = 0; i <= 9; i++) f[1][i][i % P]++;
    	
    	for (int i = 2; i < N; i++) {
    		for (int j = 0; j <= 9; j++) {
    			for (int k = 0; k < P; k++) {
    				for (int x = 0; x <= 9; x++) {
    					f[i][j][k] += f[i - 1][x][mod(k - j, P)];
    				}
    			}
    		}
    	}
    }
    
    int dp(int n) {
    	if (!n) return 1;
    	
    	vector<int> nums;
    	while (n) nums.push_back(n % 10), n /= 10;
    	
    	int res = 0, last = 0;
    	for (int i = nums.size() - 1; i >= 0; i--) {
    		int x = nums[i];
    		for (int j = 0; j < x; j++) res += f[i + 1][j][mod(-last, P)];
    		
    		last += x;
    		
    		if (!i && last % P == 0) res++;
    	}
    	
    	return res;
    }
    
    int main() {
    	while (cin >> l >> r >> P) {
    		init();
    		
    		cout << dp(r) - dp(l - 1) << endl;
    	}
    	
        return 0;
    }
    

    AcWing 1085. 不要62

    这个题目就可以独立做出来了,不要(4)和不要(62),那(f[i][j])表示为设成一共有(i)位,且最高位为(j)的数的集合,用(last)表示上一位数,用(x)表示当前枚举的数,如果(last = 6) 并且 (x = 2)或者(x = 4)就是不符合条件的。

    // Problem: 不要62
    // Contest: AcWing
    // URL: https://www.acwing.com/problem/content/1087/
    // Memory Limit: 64 MB
    // Time Limit: 1000 ms
    // 
    // Powered by CP Editor (https://cpeditor.org)
    
    #include <bits/stdc++.h>
    
    using namespace std;
    
    const int N = 11;
    int f[N][N]; //一共有i位,并且最高位为j的方案集合
    
    int l, r;
    
    void init() {
    	memset(f, 0, sizeof f);
    	
    	for (int i = 0; i <= 9; i++) {
    		if (i != 4) f[1][i] = 1;
    	}
    	
    	for (int i = 2; i < N; i++) {
    		for (int j = 0; j <= 9; j++) {
    			for (int k = 0; k <= 9; k++) {
    				if (j == 4 || k == 4) continue;
    				if (j == 6 && k == 2) continue;
    				f[i][j] += f[i - 1][k];
    			}
    		}
    	}
    }
    
    int dp(int n) {
    	if (!n) return 1;
    	
    	vector<int> nums;
    	while (n) nums.push_back(n % 10), n /= 10;
    	int res = 0, last = 0; //last记录上一位数字是啥玩意
    	
    	for (int i = nums.size() - 1; i >= 0; i--) {
    		int x = nums[i];
    		for (int j = 0; j < x; j++) {
    			if ((j == 4) || (last == 6 && j == 2)) continue;
    			res += f[i + 1][j];
    		}
    		
    		if (x == 4 || last == 6 && x == 2) break;
    		last = x;
    		
    		if (!i) res++;
    	}
    	
    	return res;
    }
    
    int main() {
    	while (cin >> l >> r, l) {
    		init();
    		
    		cout << dp(r) - dp(l - 1) << endl;	
    	}
    	
        return 0;
    }
    
  • 相关阅读:
    css中的元素旋转
    display:inlineblock的深入理解
    js时间获取。
    长英文自动换行的最终解决方法
    jqery图片展示效果
    链接A引发的思考
    电子邮件制作规范和建议
    overflow与textindent:9999px 字体隐藏及input value偏移
    jQuery load的详解
    转载:前端调试利器DebugBa
  • 原文地址:https://www.cnblogs.com/ZhengLijie/p/15243421.html
Copyright © 2020-2023  润新知