数位DP总结

数位DP

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

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

原来数位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(xlast)>=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

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

nums里从低到高存了一个数n的各位数字,所以倒序枚举,x表示当前枚举到的那一位,对于题目的要求需满足,(an1+an2+x+...+())ModN=0,我们用last记录前边位数的和,last=an1+an2,那么再DP的过程根据上面的要求,我们需要满足:(x+...+())ModN=lastModN,注意因为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;
}
posted @   Xxaj5  阅读(72)  评论(2编辑  收藏  举报
编辑推荐:
· AI与.NET技术实操系列:基于图像分类模型对图像进行分类
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
阅读排行:
· 25岁的心里话
· 闲置电脑爆改个人服务器(超详细) #公网映射 #Vmware虚拟网络编辑器
· 零经验选手,Compose 一天开发一款小游戏!
· 因为Apifox不支持离线,我果断选择了Apipost!
· 通过 API 将Deepseek响应流式内容输出到前端
点击右上角即可分享
微信分享提示