一本通数位DP小记

数位DP

「一本通 5.3 例 1」Amount of Degrees
先将这个数化为 \(B\) 进制
从高位向低位枚举: 设当前为第 i 位
这一位贡献为 从高位到第 i+1 位 都是原来的书的前若干位, 第 i 位小于原数的第 i 位且满足题目的条件, 低位到第 i-1 位 任意
(最后一位的贡献需要单独考虑)
第 i 位:如果填0则答案为 \(C_{i=剩下的个数}^{剩下的1}\)
如果点1则答案为 \(C_{i}^{剩下的1的个数-1}\)
注意一些边界情况等

点击查看代码
// 先转化为前缀和
// 从头到尾枚举每一位: 这一位的贡献为前面的位都相同, 这一位比原来更小, 后面的位任意的方案数
#include <vector>
#include <stdio.h>
#include <string.h>
int k, b;
int C[35][35]; // 组合数
int dp(int n) {
	if(!n) return 0;
	std::vector<int> num;
	while(n) num.push_back(n % b), n /= b;
	n = num.size();
	int res = 0, last = 0; // res 为答案, last 为当前 1 的个数
	for(int i = n - 1; i >= 0; i --) {
		int x = num[i];
		if(x) { // 这一位非0: 可以选择
			res += C[i][k - last]; // 这一位填0的情况(假设前面若干位都是)
			if(x > 1) { // 这一位可以尝试填1
				if(k - last - 1 >= 0) res += C[i][k - last - 1];
				break; // 由于只能填0或1,则
			} else // 只能选0: 如果1过多,跳过
				if(++ last > k) break;
		}
		if(!i && last == k) res ++; // 最后一位的贡献
	}
	return res;
}
int main() {
	int l, r; scanf("%d%d%d%d", &l, &r, &k, &b);
	for(int i = 0; i <= 34; i ++) {
		C[i][0] = 1;
		for(int j = 1; j <= i; j ++)
			C[i][j] = C[i-1][j] + C[i-1][j-1];
	}
	printf("%d\n", dp(r) - dp(l - 1));
	return 0;
}

「一本通 5.3 例 2」数字游戏

类似之前的问题计算贡献(从高向低)
f[i][j] 表示共有 i 位, 最高位为 j 的答案
枚举位来统计

点击查看代码
#include <vector>
#include <stdio.h>
#include <string.h>
int f[15][10]; // 共有 i 位, 最高位为 j 的答案
int dp(int n) {
	if(!n) return 1; // 0满足条件
	std::vector<int> num;
	while(n) num.push_back(n % 10), n /= 10;
	int res = 0, last = 0; // last 为上一位
	for(int i = num.size() - 1; i >= 0; i --) {
		int x = num[i];
		for(int j = last; j < x; j ++) res += f[i+1][j]; // 第i位填 j, 后面的随便填
		if(x < last) break; // 不能够保留
		last = x; if(!i) res ++; // 0满足条件
	}
	return res;
}
int main() {
	for(int i = 0; i < 10; i ++) f[1][i] = 1;
	for(int i = 2; i < 15; i ++)
		for(int j = 0; j < 10; j ++)
			for(int k = j; k < 10; k ++) f[i][j] += f[i-1][k];
	int l, r; while(scanf("%d%d", &l, &r) == 2) printf("%d\n", dp(r) - dp(l-1));
	return 0;
}

「一本通 5.3 例 3」Windy 数

类似的,设 f[i][j] 表示总共 i 位数, 首位为 j 的方案数

点击查看代码
#include <math.h>
#include <vector>
#include <stdio.h>
#include <string.h>
int f[15][10]; // 总共 i 位数, 首位为 j 的方案数
int dp(int n) {
	if(!n) return 0; // 0不算
	std::vector<int> num;
	while(n) num.push_back(n % 10), n /= 10;
	int res = 0, last = -2;
	for(int i = num.size() - 1; i >= 0; i --) {
		int x = num[i];
		for(int j = (i == num.size() - 1); j < x; j ++) // 首位不能是0
			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 < (int)num.size(); i ++)
		for(int j = 1; j < 10; j ++) res += f[i][j]; // 位数不足的
	return res;
}
int main() {
	for(int i = 0; i < 10; i ++) f[1][i] = 1;
	for(int i = 2; i < 15; i ++)
		for(int j = 0; j < 10; j ++)
			for(int k = 0; k < 10; k ++)
				if(abs(j - k) >= 2) f[i][j] += f[i-1][k];
	int l, r; scanf("%d%d", &l, &r);
	printf("%d\n", dp(r) - dp(l - 1));
	return 0;
}

「一本通 5.3 练习 1」数字游戏

同理,设 f[i][j][k] 总共有 i 位, 最高位为 j, 数字和 mod n 为 k 的方案数

点击查看代码
#include <vector>
#include <stdio.h>
#include <string.h>
int f[15][10][100]; // 总共有 i 位, 最高位为 j, 数字和 mod n 为 k 的方案数
int l, r, mod;
int dp(int n) {
	if(!n) return 1;
	std::vector<int> num;
	while(n) num.push_back(n % 10), n /= 10;
	int res = 0, sum = 0; // sum 为前面若干数的和
	for(int i = num.size() - 1; i >= 0; i --) {
		int x = num[i];
		for(int j = 0; j < x; j ++)
			res += f[i+1][j][(mod - sum) % mod];
		(sum += x) %= mod;
		if(!i && sum == 0) res ++;
	}
	return res;
}
int main() {
	while(scanf("%d%d%d", &l, &r, &mod) == 3) {
		memset(f, 0, sizeof(f));
		for(int i = 0; i < 10; i ++) f[1][i][i % mod] = 1;
		for(int i = 2; i < 15; i ++)
			for(int j = 0; j < 10; j ++)
				for(int k = 0; k < mod; k ++)
					for(int t = 0; t < 10; t ++)
						f[i][j][k] += f[i-1][t][((k - j) % mod + mod) % mod];
		printf("%d\n", dp(r) - dp(l - 1));
	}
	return 0;
}

不要 62

点击查看代码
#include <vector>
#include <stdio.h>
#include <string.h>
int f[10][10]; // 有 i 位数, 最高位是 j 的方案数
int dp(int n) {
    if (!n)
        return 1;

    std::vector<int> num;

    while (n)
        num.push_back(n % 10), n /= 10;

    int last = 0, res = 0;

    for (int i = num.size() - 1; i >= 0; i --) {
        int x = num[i];

        for (int j = 0; j < x; j ++)
            if (j != 4 && (last != 6 || j != 2))
                res += f[i + 1][j];

        if (x == 4 || (last == 6 && x == 2))
            break;

        if (!i)
            res ++;

        last = x;
    }

    return res;
}
int main() {
    for (int i = 0; i < 10; i ++)
        f[1][i] = i != 4;

    for (int i = 2; i < 10; i ++)
        for (int j = 0; j < 10; j ++)
            if (j != 4)
                for (int k = 0; k < 10; k ++)
                    if (j != 6 || k != 2)
                        f[i][j] += f[i - 1][k];

    int l, r;

    while (scanf("%d%d", &l, &r) == 2 && (l || r))
        printf("%d\n", dp(r) - dp(l - 1));

    return 0;
}

恨 7 不成妻
(暂缺)

P2602 数字计数

点击查看代码
#include <vector>
#include <stdio.h>
#include <string.h>
typedef long long LL;
LL calc(const std::vector<int> &vec, int l, int r) {
    LL res = 0;

    for (int i = r; i >= l; i --)
        res = res * 10 + vec[i];

    return res;
}
LL pow10[19], l, r;
LL dp(LL n, int x) {
    if (!n)
        return 0;

    std::vector<int> num;

    while (n)
        num.push_back(n % 10), n /= 10;

    n = num.size();
    LL res = 0;

    // 求出 x 在每一位上出现的次数
    for (int i = n - 1 - !x; i >= 0; i --) { // 最高位不能是 0
        if (i < n - 1) {
            res += calc(num, i + 1, n - 1) * pow10[i]; // 前面若干位 <= n的前面若干位的情况

            if (!x)
                res -= pow10[i]; // 最高若干位不能全为 0
        }

        if (num[i] == x)
            res += calc(num, 0, i - 1) + 1; // 前面若干位 = n的前面若干位的情况
        else if (num[i] > x)
            res += pow10[i];
    }

    return res;
}
int main() {
    for (int i = *pow10 = 1; i <= 18; i ++)
        pow10[i] = pow10[i - 1] * 10;

    scanf("%lld%lld", &l, &r);

    for (int x = 0; x < 10; x ++)
        printf("%lld ", dp(r, x) - dp(l - 1, x));

    return 0;
}
posted @ 2022-10-14 21:05  azzc  阅读(25)  评论(0编辑  收藏  举报