数位 dp

简介

解决大区间 \([L, R]\) 中满足条件的元素的个数的统计问题。

通常使用记忆化搜索实现,尤其注意前导零与上界答案的情况。

例题

HDU2089 不要62

人机题。

注意特判 \(l = 0\) 的情况。

代码:

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#define int long long
using namespace std;

const int N = 20;
int l, r, len, a[N], dp[N][N];

inline int dfs(int pos, int limit, int last) {
	if(! pos) return 1;
	
	if(! limit && dp[pos][last] != -1) return dp[pos][last];
	
	int res = 0, upper = limit ? a[pos] : 9;
	
	for(int i = 0 ; i <= upper ; ++ i)
		if(i != 4 && (last != 6 || i != 2)) res += dfs(pos - 1, limit && i == upper, i);
		
	if(! limit) dp[pos][last] = res;
	
	return res;
}

inline int solve(int x) {
	if(x == -1) return 0;
	
	len = 0;
	
	while(x) a[++ len] = x % 10, x /= 10;
	
	return dfs(len, true, 11);
}

signed main() {
	ios_base :: sync_with_stdio(NULL);
	cin.tie(nullptr);
	cout.tie(nullptr);
	
	memset(dp, -1, sizeof dp);
	
	while(true) {
		cin >> l >> r;
		
		if(! l && ! r) return 0;
		
		cout << solve(r) - solve(l - 1) << '\n';
	}
	
	return 0;
}

HDU5179 beautiful number

注意先判当前填的数是否为 \(0\)

代码:

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#define int long long
using namespace std;

const int N = 20;
int T, l, r, len, a[N], dp[N][N];

inline int dfs(int pos, bool limit, bool lead0, int last) {
//	cerr << "value:" << last << '\n';
	
	if(! pos) return 1;
	
	if(! limit && ! lead0 && dp[pos][last] != -1) return dp[pos][last];
	
	int res = 0, upper = limit ? a[pos] : 9;
	
	for(int i = 0 ; i <= upper ; ++ i) {
		if(i && last % i == 0) res += dfs(pos - 1, limit && i == upper, false, i);
		else if(! i && lead0) res += dfs(pos - 1, limit && i == upper, true, i);
	}
	
	if(! limit && ! lead0) dp[pos][last] = res;
	
	return res;
}

inline int solve(int x) {
	len = 0;
	
	while(x) a[++ len] = x % 10, x /= 10;
	
	return dfs(len, true, true, 0);
}

signed main() {
	ios_base :: sync_with_stdio(NULL);
	cin.tie(nullptr);
	cout.tie(nullptr);
	
	memset(dp, -1, sizeof dp);
	
	cin >> T;
	
	while(T --) {
		cin >> l >> r;
		
		cout << solve(r) - solve(l - 1) << '\n';
	}
	
	return 0;
}

P13085 [SCOI2009] windy 数(加强版)

记录前导零,当前是否有限制和上一位选的数字即可。

代码:

#include <bits/stdc++.h>
#define int long long
using namespace std;

const int N = 20;
int l, r, a[N], dp[N][N];

inline int dfs(int pos, int limit, int lead0, int last) {
	if(! pos) return 1;
	
	if(! limit && ! lead0 && dp[pos][last] != -1) return dp[pos][last];
	
	int upper = limit ? a[pos] : 9, res = 0;
	
	for(int i = 0 ; i <= upper ; ++ i) {
		if(lead0 && ! i) res += dfs(pos - 1, limit && i == upper, true, 11);
		else if(abs(i - last) >= 2) res += dfs(pos - 1, limit && i == upper, false, i);
	}
	
	if(! limit && ! lead0) dp[pos][last] = res;
	
	return res;
}

inline int solve(int x) {
	int len = 0;
	
	while(x) a[++ len] = x % 10, x /= 10;
	
	return dfs(len, true, true, 11);
}

signed main() {
	ios_base :: sync_with_stdio(NULL);
	cin.tie(nullptr);
	cout.tie(nullptr);

	memset(dp, -1, sizeof dp);
	
	cin >> l >> r;
	
	cout << solve(r) - solve(l - 1);
	
	return 0;
}

P6218 [USACO06NOV] Round Numbers S

记录 \(0\) 出现的次数和 \(1\) 出现的次数即可。

代码:

#include <bits/stdc++.h>
#define int long long
using namespace std;

const int N = 60;
int l, r, a[N], dp[N][N][N];

inline int dfs(int pos, bool limit, bool lead0, int cnt0, int cnt1) {
	if(! pos) return cnt0 >= cnt1;
	
	if(! limit && ! lead0 && dp[pos][cnt0][cnt1] != -1) return dp[pos][cnt0][cnt1];
	
	int upper = limit ? a[pos] : 1, res = 0;
	
	for(int i = 0 ; i <= upper ; ++ i) {
		if(lead0 && ! i) res += dfs(pos - 1, limit && i == upper, true, 0, 0);
		else res += dfs(pos - 1, limit && i == upper, false, cnt0 + (i == 0), cnt1 + (i == 1));
	}
	
	if(! limit && ! lead0) dp[pos][cnt0][cnt1] = res;
	
	return res;
}

inline int solve(int x) {
	int len = 0;
	
	while(x) a[++ len] = x % 2, x /= 2;
	
	return dfs(len, true, true, 0, 0);
}

signed main() {
	ios_base :: sync_with_stdio(NULL);
	cin.tie(nullptr);
	cout.tie(nullptr);
	
	memset(dp, -1, sizeof dp);
	
	cin >> l >> r;
	
	cout << solve(r) - solve(l - 1);
	
	return 0;
}

CF628D Magic Numbers

由于 \(l, r\) 过大,不能用整型变量储存,因而统计答案的方式需要改变。

具体地,计算 solve(r) - solve(l) 且统计 \(l\) 处是否有贡献即可。

因为题目保证 \(l, r\) 数位相同,所以不需要记录前导零的信息。

代码:

#include <bits/stdc++.h>
#define int long long
using namespace std;

const int N = 2e3 + 5;
const int mod = 1e9 + 7;
int m, d, len, a[N], dp[N][N];
string l, r;

inline bool check(string s) {
	len = s.size();
	s = " " + s;
	
	int rem = 0;
	
	for(int i = 1 ; i <= len ; ++ i) {
		if(i & 1) {
			if(s[i] - '0' == d) return false;
		}
		else if(s[i] - '0' != d) return false;
		
		rem = (rem * 10 + (s[i] - '0')) % m;
	}
	
	return rem == 0;
}

inline int dfs(int pos, bool limit, int rem) {
//	cerr << len << '\n';
	
	if(! pos) return rem == 0;
	
	if(! limit && dp[pos][rem] != -1) return dp[pos][rem];
	
	bool odd = ((len - pos + 1) & 1);
	int upper = limit ? a[pos] : 9, res = 0;
	
	if(odd) {
		for(int i = 0 ; i <= upper ; ++ i)
			if(i != d) res = (res + dfs(pos - 1, limit && i == upper, (rem * 10 + i) % m)) % mod;
	}
	else if(d <= upper) res = (res + dfs(pos - 1, limit && d == upper, (rem * 10 + d) % m)) % mod;
	
	if(! limit) dp[pos][rem] = res;
	
	return res;
}

inline int solve(string s) {
	len = s.size();
	s = " " + s;
	
	for(int i = 1 ; i <= len ; ++ i)
		a[len - i + 1] = s[i] - '0';
		
	return dfs(len, true, 0);
}

signed main() {
	ios_base :: sync_with_stdio(NULL);
	cin.tie(nullptr);
	cout.tie(nullptr);
	
	memset(dp, -1, sizeof dp);
	
	cin >> m >> d >> l >> r;
	
	cout << (solve(r) - solve(l) + check(l) + mod) % mod;

	return 0;
}

P4124 [CQOI2016] 手机号码

复杂但是简单。

记录前 \(1~2\) 次出现的数字,当前是否合法和 \(4, 8\) 是否出现过即可。

代码:

#include <bits/stdc++.h>
#define int long long
using namespace std;

const int N = 20;
int l, r, len, a[N], dp[N][N][N][2][2][2];

inline int dfs(int pos, bool limit, bool have4, bool have8, bool ok, int last1, int last2) {
	if(! pos) return ok && ! (have4 && have8);
	
	if(! limit && dp[pos][last1][last2][have4][have8][ok] != -1) return dp[pos][last1][last2][have4][have8][ok];
	
	int lower = (pos == len) ? 1 : 0;
	int upper = limit ? a[pos] : 9, res = 0;
	
	for(int i = lower ; i <= upper ; ++ i) {
		bool flag;
		
		if(i == last1 && i == last2) flag = true;
		else flag = false;
		if((i == 4 && have8) || (i == 8 && have4)) continue;
		if(i == 4) res += dfs(pos - 1, limit && i == upper, true, false, flag | ok, last2, i);
		else if(i == 8) res += dfs(pos - 1, limit && i == upper, false, true, flag | ok, last2, i);
		else res += dfs(pos - 1, limit && i == upper, have4, have8, flag | ok, last2, i);
	}
	
	if(! limit) dp[pos][last1][last2][have4][have8][ok] = res;
	
	return res;
}

inline int solve(int x) {
	if(x <= 1e10) return 0;
	
	len = 0;
	
	while(x) a[++ len] = x % 10, x /= 10;
	
	return dfs(len, true, false, false, false, 11, 11);
}

signed main() {
	ios_base :: sync_with_stdio(NULL);
	cin.tie(nullptr);
	cout.tie(nullptr);

	memset(dp, -1, sizeof dp);
	
	cin >> l >> r;
	
	cout << solve(r) - solve(l - 1);

	return 0;
}

CF1036C Classy Numbers

注意数字是可以重复的,所以比较傻逼。

代码:

#include <bits/stdc++.h>
#define int long long
using namespace std;

const int N = 100;
int T, l, r, a[N], dp[N][N];

inline int dfs(int pos, bool limit, bool lead0, int cnt) {
    if(! pos) return cnt <= 3;

    if(! limit && ! lead0 && dp[pos][cnt] != -1) return dp[pos][cnt];

    int res = 0, upper = limit ? a[pos] : 9;

    for(int i = 0 ; i <= upper ; ++ i) {
        if(! i && lead0) res += dfs(pos - 1, limit && i == upper, true, 0);
        else if(! i) res += dfs(pos - 1, limit && i == upper, false, cnt);
        else res += dfs(pos - 1, limit && i == upper, false, cnt + 1);
    }

    if(! limit && ! lead0) dp[pos][cnt] = res;

    return res;
}

inline int solve(int x) {
    int len = 0;

    while(x) a[++ len] = x % 10, x /= 10;

    return dfs(len, true, true, 0);
}

signed main() {
    ios_base :: sync_with_stdio(NULL);
    cin.tie(nullptr);
    cout.tie(nullptr);

    memset(dp, -1, sizeof dp);

    cin >> T;

    while(T --) {
        cin >> l >> r;

        cout << solve(r) - solve(l - 1) << '\n';
    }

    return 0;
}

P2518 [HAOI2010] 计数

P4067 [SDOI2016] 储能表

AT_abc336_e [ABC336E] Digit Sum Divisible

AT_abc317_f [ABC317F] Nim

AT_abc194_f [ABC194F] Digits Paradise in Hexadecimal

CF1994G Minecraft

posted @ 2025-08-03 03:03  endswitch  阅读(12)  评论(0)    收藏  举报