数位DP总结

前言

数位dp专题结束了,基本上各类数位dp都见过了一遍,其实当成记忆化搜索来写代码难度会小很多,而且很模板,汇总了一下最近写的数位dp题目,有些题目因为精力原因题解还未补上。

题目归纳

关于数位dp的一些题:
SPOJ BALNUM Balanced Numbers
HDU 4507 吉哥系列故事——恨7不成妻
HDU 4734 F(x)
HDU 3652 B-number
HDU 3709 Balanced Number
POJ 3252 Round Numbers
HDU 3555 Bomb
HDU 4352 XHXJ’s LIS
HDU 2089 不要62
CodeForces 55D Beautiful numbers
P1836 数页码
P6371 [COCI2006-2007#6] V
SP17247 PR003004 - Digit Sum
P3413 SAC#1 - 萌数
SP3928 MDIGITS - Counting Digits
CF628D Magic Numbers
CF855E Salazar Slytherin’s Locket
CF95D Horse Races
P4999 烦人的数学作业
P6218 [USACO06NOV] Round Numbers S
P2657 [SCOI2009] windy 数
P4317 花神的数论题
P2602 [ZJOI2010]数字计数
P4127 [AHOI2009]同类分布
P4124 [CQOI2016]手机号码

Balanced Numbers

题目链接:SPOJ - BALNUM
题目大意:一个数的数位如果每一个奇数出现了偶数次,每一个偶数出现的奇数次,这个数被称为平衡数,求解[L,R]区间的平衡数数量。
数据范围: 0 ≤ L ≤ R ≤ 1 0 19 0\le L \le R \le 10^{19} 0LR1019
题解:将数位使用的奇偶情况用一个数字 s t a t e state state来压缩表示,将数位的使用情况用另一个数字 h p hp hp来压缩表示。最后我们check一下数位的奇偶情况即可。要注意的一个点是前置0的情况要考虑进去,不然可能会导致0的使用情况是错误的。
AC代码:

#include<bits/stdc++.h>

#define ld long double
#define ll long long
using namespace std;
template<class T>
void read(T& x)
{
	T res = 0, f = 1; char c = getchar();
	while (!isdigit(c)) {
		if (c == '-')f = -1; c = getchar();
	}
	while (isdigit(c)) {
		res = (res << 3) + (res << 1) + c - '0'; c = getchar();
	}
	x = res * f;
}
#define int long long
const ll N = 200000 + 10;
const int mod = 1e9 + 7;
int w[N];
int dp[22][1050][2][1050];
int check(int x, int y)
{
	for (int i = 0; i <= 9; i++)
	{
		if ((y & (1 << i)) == 0)continue;
		int c = x & (1 << i) ? 1 : 0;
		if ((i & 1) && c)return 0;
		if (((i & 1) == 0) && !c)return 0;
	}
	return 1;
}
int dfs(int cur, int limit, int state, int _0, int hp)
{
	if (!cur)return check(state, hp);
	if (!limit && ~dp[cur][state][_0][hp])return dp[cur][state][_0][hp];
	int tp = limit ? w[cur] : 9;
	int ans = 0;
	for (int i = 0; i <= tp; i++)
	{
		ans += dfs(cur - 1, limit && i == tp, _0 && (i == 0) ? 0 : state ^ (1 << i), _0 && i == 0, _0 && (i == 0) ? 0 : hp | (1 << i));
	}
	if (!limit)dp[cur][state][_0][hp] = ans;
	return ans;
}
int find(int x)
{
	w[0] = 0;
	while (x)w[++w[0]] = x % 10, x /= 10;
	return dfs(w[0], 1, 0, 1, 0);
}
signed main()
{
	//ios::sync_with_stdio(false);
#ifndef ONLINE_JUDGE
	freopen("test.in", "r", stdin);
#endif // ONLINE_JUDGE

	int t; read(t); memset(dp, -1, sizeof(dp));
	while (t--)
	{
		int l, r; read(l), read(r);
		printf("%lld\n", find(r) - find(l - 1));
	}
	return 0;
}

吉哥系列故事——恨7不成妻

题目链接:HDU - 4507
题目大意:如果一个数满足数位包含7或者数位和整除7或者数的值整除7,我们称这个数与7相关。现在求解 [ L , R ] [L,R] [L,R]内与7无关的数的平方和。
数据范围: 1 ≤ T ≤ 50 , 1 ≤ L ≤ R ≤ 1 0 18 1 \le T \le 50,1\le L \le R \le 10^{18} 1T50,1LR1018
题解:非常好的一道题,我也是参考题解才写出来的。我们先考虑一下如何求出与7无关的数的个数。在此给出 d f s dfs dfs函数

int dfs(int cur, int limit, int sum, int _7, int num)
{
	if (!cur)
	{
		if (_7 || sum % 7 == 0 || num % 7 == 0)return 0;
		return 1;
	}
	if (!limit && ~dp[cur][sum][_7][num])return dp[cur][sum][_7][num];
	int ans=0;
	int tp = limit ? w[cur] : 9;
	for (int i = 0; i <= tp; i++)
	{
		ans += dfs(cur - 1, limit && i == tp, (sum + i) % 7, _7 || i == 7, (num * 10 + i) % 7);
	}
	if (!limit)dp[cur][sum][_7][num] = ans;
	return ans;
}

其中_7 表示是否有数位等于7, s u m sum sum表示数位和, n u m num num表示数字的数值。这样我们就可以求出与7无关的数字的个数。
我们继续考虑如何去求解与7无关的数字的和。写出 d f s dfs dfs函数

int dfs(int cur, int limit, int sum, int _7, int num)
{
	if (!cur)
	{
		if (_7 || sum % 7 == 0 || num % 7 == 0)return 0;
		return num;
	}
	if (!limit && ~dp[cur][sum][_7][num])return dp[cur][sum][_7][num];
	int ans=0;
	int tp = limit ? w[cur] : 9;
	for (int i = 0; i <= tp; i++)
	{
		ans += dfs(cur - 1, limit && i == tp, (sum + i) % 7, _7 || i == 7, num * 10 + i);
	}
	if (!limit)dp[cur][sum][_7][num] = ans;
	return ans;
}

n u m num num没有取模,直接返回,不过这会导致我们的 d p dp dp数组极大。我们考虑优化,在一个 d f s dfs dfs过程中,我们返回的 a n s ans ans是指当前 c u r cur cur位,数位和为 s u m sum sum,数值为 n u m num num的与7无关的数字和。我们将其拆分为两部分 1 0 c u r + n e x t 10^{cur}+next 10cur+next,同一个dfs函数中的 1 0 c u r 10^{cur} 10cur是不变的,我们只需要将 1 0 c u r ∗ 满 足 的 个 数 + n e x t 10^{cur}*满足的个数+next 10cur+next即可。同理,对于平方和,即 ( 1 0 c u r + n e x t ) 2 (10^{cur}+next)^2 (10cur+next)2,我们对其拆分,得到 1 0 c u r ∗ 1 0 c u r + 2 ∗ n e x t ∗ 1 0 c u r + n e x t 2 10^{cur}*10^{cur}+2*next*10^{cur}+next^2 10cur10cur+2next10cur+next2。可以看到我们需要依赖与7无关的个数与数字和,所以我们采用结构体来进行结果传递。具体看代码。
AC代码:

#include<bits/stdc++.h>

#define ld long double
#define ll long long
using namespace std;
template<class T>
void read(T& x)
{
	T res = 0, f = 1; char c = getchar();
	while (!isdigit(c)) {
		if (c == '-')f = -1; c = getchar();
	}
	while (isdigit(c)) {
		res = (res << 3) + (res << 1) + c - '0'; c = getchar();
	}
	x = res * f;
}
#define int long long
const ll N = 200000 + 10;
const int mod = 1e9 + 7;
int w[25], p[N];
struct node
{
	int num, sum, sum2;
	node()
	{
		num = 0; sum = 0; sum2 = 0;
	}
};
node dp[25][8][2][8];

node dfs(int cur, int limit, int sum, int _7, int num)
{
	if (!cur)
	{
		node s;
		if (_7 || num % 7 == 0 || sum % 7 == 0)
		{
			s.num = 0;
		}
		else
			s.num = 1;
		return s;
	}
	if (!limit && ~dp[cur][sum][_7][num].num)return dp[cur][sum][_7][num];
	node ans;
	int tp = limit ? w[cur] : 9;
	for (int i = 0; i <= tp; i++)
	{
		node tes = dfs(cur - 1, limit && i == tp, (sum + i) % 7, _7 || i == 7, (num * 10 + i) % 7);
		ans.num = (ans.num + tes.num) % mod;
		ans.sum = (ans.sum + ((tes.num * i % mod) * p[cur - 1] % mod)+tes.sum) % mod;
		ans.sum2 = (ans.sum2 + tes.sum2 + (((2 * tes.sum % mod) * i % mod) * p[cur - 1] % mod)) % mod;
		ans.sum2 = (ans.sum2 + ((((tes.num * i % mod) * i % mod) * p[cur - 1] % mod) * p[cur - 1] % mod)) % mod;
	}
	if (!limit)dp[cur][sum][_7][num] = ans;
	return ans;
}
int find(int x)
{
	w[0] = 0;
	while (x)w[++w[0]] = x % 10, x /= 10;
	return dfs(w[0], 1, 0, 0, 0).sum2;
}
signed main()
{
	//ios::sync_with_stdio(false);
#ifndef ONLINE_JUDGE
	freopen("test.in", "r", stdin);
#endif // ONLINE_JUDGE
	int t; read(t);
	memset(dp, -1, sizeof(dp));
	p[0] = 1;
	for (int i = 1; i <= 20; i++)p[i] = (p[i - 1] * 10) % mod;
	while (t--)
	{
		int l, r; read(l), read(r);
		printf("%lld\n", ((find(r) - find(l - 1)) % mod + mod) % mod);
	}
	return 0;
}

F(x)

题目链接:HDU - 4734
题目大意:对于有n位数字的 x ( A n A n − 1 A n − 2 . . . A 1 ) x(A_nA_{n-1}A_{n-2}...A_1) x(AnAn1An2...A1),定义 F ( x ) = ∑ i = 1 n A i ∗ 2 i − 1 F(x)=\sum\limits_{i=1}^n{A_i*2^{i-1}} F(x)=i=1nAi2i1。给定A,B求解有 [ 0 , B ] [0,B] [0,B]中有多少数字满足 F ( i ) ≤ F ( A ) F(i)\le F(A) F(i)F(A)
数据范围: 0 ≤ T ≤ 10000 , 0 ≤ A , B < 1 0 9 0\le T\le10000,0\le A,B <10^9 0T10000,0A,B<109
题解:我们可以看出A的对于我们而言没有太多价值,我们首先计算出 F ( A ) F(A) F(A)的值,然后我们再搜索出有多少个数的 F ( x ) F(x) F(x)小于 F ( A ) F(A) F(A) c u r cur cur表示当前位数, l i m i t limit limit表示是否顶着上界, s u m sum sum表示目前 F ( A ) − 搜 索 的 数 的 F 值 F(A)-搜索的数的F值 F(A)F具体可以看代码。不过要注意一个点, s u m < 0 sum<0 sum<0时就可以退出了,这样可以防止因为访问负下标数组而导致的奇奇怪怪的错误。
AC代码:

#include<bits/stdc++.h>

#define ld long double
#define ll long long
using namespace std;
template<class T>
void read(T& x)
{
	T res = 0, f = 1; char c = getchar();
	while (!isdigit(c)) {
		if (c == '-')f = -1; c = getchar();
	}
	while (isdigit(c)) {
		res = (res << 3) + (res << 1) + c - '0'; c = getchar();
	}
	x = res * f;
}
#define int long long
const ll N = 200000 + 10;
const int mod = 1e9 + 7;
int w[N];
int F(int x)
{
	w[0] = 0;
	while (x)w[++w[0]] = x % 10, x /= 10;
	int ans = 0;
	for (int i = 1; i <= w[0]; i++)ans += w[i] * (1ll<< (i-1));
	return ans;
}
int dp[20][15000];
int dfs(int cur, int limit, int sum)
{
	if (sum < 0)return 0;
	if (!cur)return sum >= 0;
	if (!limit && ~dp[cur][sum])return dp[cur][sum];
	int ans = 0;
	int tp = limit ? w[cur] : 9;
	for (int i = 0; i <= tp; i++)
	{
		ans += dfs(cur - 1, limit && i == tp, sum - i * (1ll << (cur-1)));
	}
	if (!limit)dp[cur][sum] = ans;
	return ans;
}
int find(int x, int FA)
{
	
	w[0] = 0;
	while (x)w[++w[0]] = x % 10, x /= 10;
	return dfs(w[0], 1, FA);
}
signed main()
{
	//ios::sync_with_stdio(false);
#ifndef ONLINE_JUDGE
	freopen("test.in", "r", stdin);
#endif // ONLINE_JUDGE
	memset(dp, -1, sizeof(dp));
	int t; read(t); int qiu = 0;
	while (t--)
	{
		int A, B; read(A), read(B);
		printf("Case #%lld: %lld\n",++qiu, find(B, F(A)));
	}
	return 0;
}

B-number

题目链接:HDU - 3652
题目大意:求 [ 1 , n ] [1,n] [1,n]中满足包含13且可以整除13的数字个数。
数据范围: 1 < = n < = 1 0 9 1 <= n <= 10^9 1<=n<=109
题解:比较经典的数位dp模型了,直接用 n u m num num表示数字模13的值, p r e pre pre表示上一次的数字, 1 3 _13 13表示是否已经包含了13了。然后我们对应转移即可。
AC代码:

#include<bits/stdc++.h>

#define ld long double
#define ll long long
using namespace std;
template<class T>
void read(T& x)
{
	T res = 0, f = 1; char c = getchar();
	while (!isdigit(c)) {
		if (c == '-')f = -1; c = getchar();
	}
	while (isdigit(c)) {
		res = (res << 3) + (res << 1) + c - '0'; c = getchar();
	}
	x = res * f;
}
const ll N = 200000 + 10;
const int mod = 1e9 + 7;
int w[20], dp[15][15][11][2];
int dfs(int cur, int limit, int num,int pre, bool _13)
{
	if (!cur)
	{
		return _13 && (num % 13 == 0);
	}
	if (!limit && ~dp[cur][num][pre][_13])return dp[cur][num][pre][_13];
	int ans = 0;
	int tp = limit ? w[cur] : 9;
	for (int i = 0; i <= tp; i++)
		ans += dfs(cur - 1, limit && i == tp, (num * 10 + i) % 13, i, _13 || (pre == 1 && i == 3));
	if (!limit)
		dp[cur][num][pre][_13] = ans;
	return ans;
}
int find(int x)
{
	w[0] = 0;
	while (x)w[++w[0]] = x % 10, x /= 10;
	return dfs(w[0], 1, 0, 10, 0);
}
int main()
{
	//ios::sync_with_stdio(false);
#ifndef ONLINE_JUDGE
	freopen("test.in", "r", stdin);
#endif // ONLINE_JUDGE
	int n; memset(dp, -1, sizeof(dp));
	while (scanf("%d", &n) != EOF)
	{
		printf("%d\n", find(n));
	}
	return 0;
}

Balanced Number

题目链接: HDU - 3709
题目大意:一个数如果可以以某一个数位作为支点,使得左右力矩相等,则这个数是平衡的。求给定 [ L , R ] [L,R] [L,R]中平衡数的个数。
数据范围: 0 ≤ x ≤ y ≤ 1 0 18 , 0 < T ≤ 30 0 ≤ x ≤ y ≤ 10^{18},0 < T ≤ 30 0xy1018,0<T30
题解:蛮有思维的数位dp,首先我们要想到枚举支点所在的位数。这里先给出一个容易想到搜索写法。

int w[50], dp[20][1600][1600];
int dfs(int cur, int limit, int left,int right,int ph)
{
	if (!cur)
	{
		return left==right;
	}
	if (!limit && ~dp[cur][left][right])return dp[cur][left][right];
	int ans = 0;
	int tp = limit ? w[cur] : 9;
	for (int i = 0; i <= tp; i++)
	{
		ans += dfs(cur - 1, limit && (i == tp),  cur<=ph?left+i*(ph-cur):left, cur <= ph ? right : right + i * (cur - ph), ph);
	}
	if (!limit)
		dp[cur][left][right] = ans;
	return ans;
}
int find(int x)
{
	if (x <0)return 0;
	if (x == 0)return 1;
	w[0] = 0;
	while (x)w[++w[0]] = x %10, x /= 10;
	int ans = 0;
	for (int i = 1; i <= w[0]; i++)
	{
		memset(dp, -1, sizeof(dp));
		ans += dfs(w[0], 1, 0, 0, i);
	}
	return ans-w[0]+1;//0被多计数了,每次都计算了
}

d f s dfs dfs函数中, c u r cur cur表示剩余的位数, l i m i t limit limit表示是否顶着上界, l e f t left left表示左边力矩大小, r i g h t right right表示右边力矩大小, p h ph ph表示支点所在位。这样我们只要最后判断 l e f t = = r i g h t left==right left==right就行。不过我们发现这样写数组过大!而且每次都要 m e m s e t ( d p , − 1 , s i z e o f ( d p ) ) memset(dp, -1, sizeof(dp)) memset(dp,1,sizeof(dp)) T L E TLE TLE。我们考虑将 l e f t left left r i g h t right right改成 s u m = r i g h t − l e f t sum=right-left sum=rightleft,这样数组就可以少一维,这样就完全ok了
AC代码:

#include<bits/stdc++.h>

#define ld long double
#define ll long long
using namespace std;
template<class T>
void read(T& x)
{
	T res = 0, f = 1; char c = getchar();
	while (!isdigit(c)) {
		if (c == '-')f = -1; c = getchar();
	}
	while (isdigit(c)) {
		res = (res << 3) + (res << 1) + c - '0'; c = getchar();
	}
	x = res * f;
}
#define int long long
const ll N = 200000 + 10;
const int mod = 1e9 + 7;
int w[50], dp[20][1600][20];
int dfs(int cur, int limit, int sum,int ph)
{
	if (!cur)
	{
		return sum==0;
	}
	if (!limit && ~dp[cur][sum][ph])return dp[cur][sum][ph];
	int ans = 0;
	int tp = limit ? w[cur] : 9;
	for (int i = 0; i <= tp; i++)
	{
		ans += dfs(cur - 1, limit && (i == tp), sum + (cur - ph)*i,ph);
	}
	if (!limit)
		dp[cur][sum][ph] = ans;
	return ans;
}
int find(int x)
{
	if (x <0)return 0;
	
	w[0] = 0;
	while (x)w[++w[0]] = x %10, x /= 10;
	int ans = 0;
	for (int i = 1; i <= w[0]; i++)
	{
		ans += dfs(w[0], 1, 0, i);
	}
	return ans - w[0] + 1;
}
signed main()
{
	//ios::sync_with_stdio(false);
#ifndef ONLINE_JUDGE
	freopen("test.in", "r", stdin);
#endif // ONLINE_JUDGE
	memset(dp, -1, sizeof(dp));
	int t; read(t);
	while (t--)
	{
		ll l, r; read(l), read(r);
		printf("%lld\n", find(r) - find(l - 1));
	}
	
	return 0;
}

Round Numbers

题目链接: POJ - 3252
题目大意:给定 [ L , R ] [L,R] [L,R]求区间内满足二进制数中0个数大于等于1个数的数字个数。
数据范围: 1 ≤ L < R ≤ 2 ∗ 1 0 9 1 ≤ L < R ≤ 2*10^9 1L<R2109
题解:数位 d p dp dp我的写法就是记忆化搜索,如果看过之前题解的可以注意到我的写法几乎没什么变化,只有 d p dp dp数组和dfs函数有不同的区别。这题要注意如果有前置0,那么0的数量不能直接加一。其他的可以看代码
AC代码:

#include<vector>
#include<set>
#include<map>
#include<stack>
#include<queue>
#include<iostream>
#include<algorithm>
#include<cstdio>
#include<cstdlib>
#include<string>
#include<cstring>
#include<cmath>
#include<time.h>
#include<stdio.h>

#define ld long double
#define ll long long
using namespace std;
template<class T>
void read(T& x)
{
	T res = 0, f = 1; char c = getchar();
	while (!isdigit(c)) {
		if (c == '-')f = -1; c = getchar();
	}
	while (isdigit(c)) {
		res = (res << 3) + (res << 1) + c - '0'; c = getchar();
	}
	x = res * f;
}
#define int long long
const ll N = 200000 + 10;
const int mod = 1e9 + 7;
int w[50], dp[50][50][50][2];
int dfs(int cur, int limit, int num1,int num0, bool _0)
{
	if (!cur)
	{
		return num1 <= num0;
	}
	if (!limit && ~dp[cur][num1][num0][_0])return dp[cur][num1][num0][_0];
	int ans = 0;
	int tp = limit ? w[cur] : 1;
	for (int i = 0; i <= tp; i++)
	{
		ans += dfs(cur - 1, limit && (i == tp), num1 + (i == 1), _0 ? 0 : num0 + (i == 0), _0 && (i == 0));
	}
	if (!limit)
		dp[cur][num1][num0][_0] = ans;
	return ans;
}
int find(int x)
{
	w[0] = 0;
	while (x)w[++w[0]] = x &1, x /= 2;
	return dfs(w[0], 1, 0, 0, 1);
}
signed main()
{
	//ios::sync_with_stdio(false);
#ifndef ONLINE_JUDGE
	freopen("test.in", "r", stdin);
#endif // ONLINE_JUDGE
	memset(dp, -1, sizeof(dp));
	ll l, r; read(l), read(r);
	printf("%lld\n", find(r) - find(l - 1));
	return 0;
}

HDU - 3555 Bomb

题目链接:Bomb
题目大意:求 [ L , R ] [L,R] [L,R]区间内包含 49 49 49的有多少。
数据范围: 1 ≤ T ≤ 1 0 5 , 1 ≤ N ≤ 2 63 − 1 1 \le T \le 10^5,1\le N \le 2^{63}-1 1T105,1N2631
题解:我们可以发现,不同的数位dp基本上改的就是dfs函数。这题我们就要维护前面的搜索结果里面是否已经包含了49,我这里用 i s o k isok isok表示。然后要维护 i s o k isok isok,我们需要得到上一次的搜索数位是什么,我们用 p r e pre pre来记录。剩下的就是对应转移了。
AC代码:

#include<bits/stdc++.h>

#define ld long double
#define ll long long
using namespace std;
template<class T>
void read(T& x)
{
	T res = 0, f = 1; char c = getchar();
	while (!isdigit(c)) {
		if (c == '-')f = -1; c = getchar();
	}
	while (isdigit(c)) {
		res = (res << 3) + (res << 1) + c - '0'; c = getchar();
	}
	x = res * f;
}
#define int long long
const ll N = 200000 + 10;
const int mod = 1e9 + 7;
int w[30], dp[30][2][10][2],t;
int dfs(int cur, bool limit, int pre, bool isok)
{
	if (!cur)
	{
		return isok;
	}
	if (~dp[cur][limit][pre][isok])return dp[cur][limit][pre][isok];
	int ans = 0;
	int tp = limit ? w[cur] : 9;
	for (int i = 0; i <= tp; i++)
	{
		ans += dfs(cur - 1, limit && (i == tp),i, isok||(i == 9 && pre==4));
	}
	return dp[cur][limit][pre][isok] = ans;
}
int find(int x)
{
	memset(dp, -1, sizeof(dp));
	w[0] = 0;
	while (x)w[++w[0]] = x % 10, x /= 10;
	return dfs(w[0], 1, 0, 0);
}
signed main()
{
	//ios::sync_with_stdio(false);
#ifndef ONLINE_JUDGE
	freopen("test.in", "r", stdin);
#endif // ONLINE_JUDGE
	read(t);
	while (t--)
	{
		int x; read(x);
		printf("%lld\n", find(x));
	}

	return 0;
}

HDU - 4352 XHXJ’s LIS

题目链接:C - XHXJ’s LIS
题目大意:给定[L,R],求各位数字组成的严格上升子序列的长度为K的个数。
数据范围: 1 ≤ L ≤ R ≤ 2 63 − 1 , 1 ≤ K ≤ 10 1\le L\le R\le 2^{63}-1,1\le K \le 10 1LR2631,1K10
题解:我们用一个10位二进制状态来表示最长上升子序列,状态的修改参考导弹拦截题目里面给出的 n l o g n nlogn nlogn修改的方法。其他的就是数位dp了。
AC代码:

#include<bits/stdc++.h>

#define ld long double
#define ll long long
using namespace std;
template<class T>
void read(T& x)
{
	T res = 0, f = 1; char c = getchar();
	while (!isdigit(c)) {
		if (c == '-')f = -1; c = getchar();
	}
	while (isdigit(c)) {
		res = (res << 3) + (res << 1) + c - '0'; c = getchar();
	}
	x = res * f;
}
#define int long long
const ll N = 200000 + 10;
const int mod = 1e9 + 7;
int t, l, r, K;
int dp[20][2][1 << 11][11], w[21];
int getnum(int x)
{
	int ans = 0;
	while (x)
	{
		ans++;
		x -= x & -x;
	}
	return ans;
}
int updatestate(int x, int st)
{
	for (int i = x; i <= 9; i++)
	{
		if (st & (1 << i))return (st ^ (1 << i)) | (1 << x);
	}
	return st | (1 << x);
}
int dfs(int cur, int limit, int _0, int state)
{
	if (!cur)return getnum(state) == K;
	if (!limit && ~dp[cur][_0][state][K])return dp[cur][_0][state][K];
	int ans = 0;
	int tp = limit ? w[cur] : 9;
	for (int i = 0; i <= tp; i++)
	{
		ans += dfs(cur - 1, limit && i == tp, _0 && i == 0, (_0 && i == 0) ? 0 : updatestate(i, state));
	}
	if (!limit)dp[cur][_0][state][K] = ans;
	return ans;
}
int find(int x)
{
	w[0] = 0;
	while (x)w[++w[0]] = x % 10, x /= 10;
	return dfs(w[0], 1, 1, 0);
}
signed main()
{
	//ios::sync_with_stdio(false);
#ifndef ONLINE_JUDGE
	freopen("test.in", "r", stdin);
#endif // ONLINE_JUDGE
	memset(dp, -1, sizeof(dp));
	read(t); int qiu = 0;
	while (t--)
	{
		read(l), read(r), read(K);
		printf("Case #%lld: %lld\n", ++qiu, find(r) - find(l - 1));
	}

	return 0;
}

HDU 2089 不要62

题目链接:不要62
题目大意:给定 [ L , R ] [L,R] [L,R]区间,问区间内不含62和4的数字的个数。
数据范围: 0 < n ≤ m < 1000000 0<n \le m <1000000 0<nm<1000000
题解:这个数据量不是很大,所以可以直接暴力 L → R L\to R LR扫一遍,看是否有62 o r or or 4。不过还是说一下我数位 d p dp dp的做法,其实简单数位 d p dp dp完全是板子的做法。即先将 [ L , R ] [L,R] [L,R]变成 [ 1 , R ] − [ 1 − L − 1 ] [1,R]-[1-L-1] [1,R][1L1]。然后用 f i n d ( x ) find(x) find(x)函数来找 [ 1 , x ] [1,x] [1,x]内满足要求的个数。我们需要在dfs中维护的有:还剩余的位数,是否顶着上界,搜索的前一个数,是否已经包含62,是否包含了4。然后我们对应转移就行了。
AC代码:

#include<bits/stdc++.h>

#define ld long double
#define ll long long
using namespace std;
template<class T>
void read(T& x)
{
	T res = 0, f = 1; char c = getchar();
	while (!isdigit(c)) {
		if (c == '-')f = -1; c = getchar();
	}
	while (isdigit(c)) {
		res = (res << 3) + (res << 1) + c - '0'; c = getchar();
	}
	x = res * f;
}
#define int long long
const ll N = 200000 + 10;
const int mod = 1e9 + 7;
int w[15], dp[10][2][10][2][2];
int dfs(int cur, int limit, int pre, bool _4, bool _62)
{
	if (!cur)
	{
		if (_4 || _62)return 0;
		return 1;
	}
	if (~dp[cur][limit][pre][_4][_62])return dp[cur][limit][pre][_4][_62];
	int ans = 0;
	int tp = limit ? w[cur] : 9;
	for (int i = 0; i <= tp; i++)
	{
		ans += dfs(cur - 1, limit && i == tp, i, _4 || i == 4, _62 || (i == 2 && pre == 6));
	}
	return dp[cur][limit][pre][_4][_62] = ans;
}
int find(int x)
{
	memset(dp, -1, sizeof(dp));
	w[0] = 0;
	while (x)w[++w[0]] = x % 10, x /= 10;
	return dfs(w[0], 1, 0, 0, 0);
}
signed main()
{
	//ios::sync_with_stdio(false);
#ifndef ONLINE_JUDGE
	freopen("test.in", "r", stdin);
#endif // ONLINE_JUDGE
	while (1)
	{
		int l, r; read(l), read(r);
		if (!l && !r)break;
		printf("%lld\n", find(r) - find(l - 1));
	}

	return 0;
}

CodeForces - 55D Beautiful numbers

题目链接:Beautiful numbers
题目大意:求 [ L , R ] [L,R] [L,R]区间内包含可以被自身所有数位整除的有多少。
数据范围: 1 ≤ t ≤ 10 , 1 ≤ l ≤ r ≤ 9 ∗ 1 0 18 1\le t \le 10,1\le l\le r\le 9*10^{18} 1t10,1lr91018
题解:非常好的一道题,数位dp进阶必做。首先我们依靠前两题来写一下我们的模板dfs函数

int dfs(int cur, bool limit, int premul, int prenum)
{
	if (!cur)
	{
		return prenum % premul == 0;
	}
	if (~dp[cur][limit][premul][prenum])return dp[cur][limit][premul][prenum];
	int ans = 0;
	int tp = limit ? w[cur] : 9;
	for (int i = 0; i <= tp; i++)
	{
		ans += dfs(cur - 1, limit && (i == tp), !i?premul:premul*i, (prenum * 10 + i));
	}
	return  dp[cur][limit][premul][prenum] = ans;
}

和我们之前写的完全一样! p r e n u m prenum prenum表示之前搜索的数, p r e m u l premul premul表示已经搜索的数位的积。这样我们就完成了这一题。不过有个问题就是dp数组的大小。我们 p r e m u l premul premul可以达到 9 ∗ 1 0 18 9*10^{18} 91018的大小!空间已经炸穿了。我们这里可以发现一个空间小优化就是我们可以预处理出 9 ! 9! 9!,边模边运算。dfs如下

int dfs(int cur, bool limit, int premul, int prenum)
{
	if (!cur)
	{
		return prenum % premul == 0;
	}
	if (~dp[cur][limit][premul][prenum] && !limit)return dp[cur][limit][premul][prenum];
	int ans = 0;
	int tp = limit ? w[cur] : 9;
	for (int i = 0; i <= tp; i++)
	{
		ans += dfs(cur - 1, limit && (i == tp), !i ? premul : premul * i, (prenum * 10 + i) % (9!));
	}
	return  dp[cur][limit][premul][prenum] = ans;
}

这样空间要求变成了 19 ∗ 2 ∗ 9 ! ∗ 9 ! 19*2*9!*9! 1929!9!掐指一算,我们还是炸空间了,然后可以可以注意到一个优化的点,就是我们可以将 p r e m u l premul premul变成 p r e l c m prelcm prelcm也就是将之前搜索到的数位积变成数位lcm。这样我们就可以将9!优化下到 l c m ( 1 , 2 , . . 9 ) = 2520 lcm(1,2,..9)=2520 lcm(1,2,..9)=2520。dfs如下

int dfs(int cur, bool limit, int prelcm, int prenum)
{
	if (!cur)
	{
		return prenum % prelcm == 0;
	}
	if (~dp[cur][limit][prelcm][prenum] && !limit)return dp[cur][limit][prelcm][prenum];
	int ans = 0;
	int tp = limit ? w[cur] : 9;
	for (int i = 0; i <= tp; i++)
	{
		ans += dfs(cur - 1, limit && (i == tp), !i ? prelcm : lcm(prelcm ,i), (prenum * 10 + i) % (2520));
	}
	return  dp[cur][limit][prelcm][prenum] = ans;
}

空间成功变成 19 ∗ 2 ∗ 2520 ∗ 2520 19*2*2520*2520 19225202520。还是超了一点。。。不过我们可以发现 p r e l c m prelcm prelcm无法取到 1 → 2520 1\to 2520 12520之间的所有数,只能取到2520的因子数。所以最后一个优化就是将 p r e l c m prelcm prelcm离散化。离散化后可以发现 p r e l c m prelcm prelcm只能取48个,所以我们空间变成了 19 ∗ 2 ∗ 50 ∗ 2520 19*2*50*2520 192502520,完全ok了。

AC代码:


#include<bits/stdc++.h>

#define ld long double
#define ll long long
using namespace std;
template<class T>
void read(T& x)
{
	T res = 0, f = 1; char c = getchar();
	while (!isdigit(c)) {
		if (c == '-')f = -1; c = getchar();
	}
	while (isdigit(c)) {
		res = (res << 3) + (res << 1) + c - '0'; c = getchar();
	}
	x = res * f;
}
#define int long long
const ll N = 200000 + 10;
int w[30], dp[20][50][2600], t, totmod;
int a[N], toa[N];
int gcd(int x, int y)
{
	return !y ? x : gcd(y, x % y);
}
int lcm(int x, int y)
{
	if (!x || !y)return x + y;
	return x * y / gcd(x, y);
}
void init()
{
	memset(dp, -1, sizeof(dp));
	totmod = 1;
	for (int i = 2; i <= 9; i++)totmod = lcm(totmod, i);
	for (int i = 1; i <= totmod; i++)
	{
		if (totmod % i == 0)a[++a[0]] = i, toa[i] = a[0];
	}
}
int dfs(int cur, bool limit, int prelcm, int prenum)
{
	if (!cur)
	{
		return prenum % prelcm == 0;
	}
	if (~dp[cur][toa[prelcm]][prenum]&&!limit)return dp[cur][toa[prelcm]][prenum];
	int ans = 0;
	int tp = limit ? w[cur] : 9;
	for (int i = 0; i <= tp; i++)
	{
		ans += dfs(cur - 1, limit && (i == tp), lcm(prelcm, i), (prenum * 10 + i) % totmod);
	}
	if (!limit)dp[cur][toa[prelcm]][prenum] = ans;
	return  ans;
}
int find(int x)
{
	w[0] = 0;
	while (x)w[++w[0]] = x % 10, x /= 10;
	return dfs(w[0], 1, 1, 0);
}
signed main()
{
	//ios::sync_with_stdio(false);
#ifndef ONLINE_JUDGE
	freopen("test.in", "r", stdin);
#endif // ONLINE_JUDGE
	init();
	read(t);
	while (t--)
	{
		int l, r; read(l), read(r);
		printf("%lld\n", find(r) - find(l - 1));
	}

	return 0;
}

P1836 数页码

题目链接:P1836 数页码
题目大意:
在这里插入图片描述
数据范围: 1 ≤ n ≤ 1 0 9 1\le n \le 10^9 1n109
题解:很简单的数位dp入门题,直接拿 s u m sum sum去记录即可。
AC代码:

#include<bits/stdc++.h>

#define ld long double
#define ll long long
using namespace std;
template<class T>
void read(T& x)
{
	T res = 0, f = 1; char c = getchar();
	while (!isdigit(c)) {
		if (c == '-')f = -1; c = getchar();
	}
	while (isdigit(c)) {
		res = (res << 3) + (res << 1) + c - '0'; c = getchar();
	}
	x = res * f;
}
#define int long long
const ll N = 200000 + 10;
const int mod = 1e9 + 7;
int w[20], dp[25][2][9 * 18 + 5];
int t;
int dfs(int cur, int limit, int sum)
{
	if (!cur)return sum;
	if (~dp[cur][limit][sum])return dp[cur][limit][sum];
	int tp = limit ? w[cur] : 9;
	int ans = 0;
	for (int i = 0; i <= tp; i++)
	{
		ans += dfs(cur - 1, limit && (i == tp), sum + i);
	}
	return dp[cur][limit][sum] = (ans );
}
int find(int x)
{
	w[0] = 0;
	while (x)w[++w[0]] = x % 10, x /= 10;
	memset(dp, -1, sizeof(dp));
	return dfs(w[0], 1, 0);
}
signed main()
{
	//ios::sync_with_stdio(false);
#ifndef ONLINE_JUDGE
	freopen("test.in", "r", stdin);
#endif // ONLINE_JUDGE
	
	int n; read(n);
	printf("%lld\n", find(n));
	return 0;
}

P4999 烦人的数学作业

题目链接:P4999 烦人的数学作业
题目大意:给出一个区间 [ L , R ] [L,R] [L,R],求L到R区间内每个数的数字和
数据范围: 1 ≤ L ≤ R ≤ 1 0 18 , 1 ≤ T ≤ 20 1 \leq L \leq R \leq 10^{18},1 \leq T \le20 1LR1018,1T20
题解:数位 d p dp dp模板题,非常经典。建议数位 d p dp dp入门可以从这个题开始。直接记忆化搜索写就行

#include<bits/stdc++.h>

#define ld long double
#define ll long long
using namespace std;
template<class T>
void read(T& x)
{
	T res = 0, f = 1; char c = getchar();
	while (!isdigit(c)) {
		if (c == '-')f = -1; c = getchar();
	}
	while (isdigit(c)) {
		res = (res << 3) + (res << 1) + c - '0'; c = getchar();
	}
	x = res * f;
}
#define int long long
const ll N = 200000 + 10;
const int mod = 1e9 + 7;
int w[20],dp[25][2][9*18+5];
int t;
int dfs(int cur, int limit, int sum)
{
	if (!cur)return sum;
	if (~dp[cur][limit][sum])return dp[cur][limit][sum];
	int tp = limit ?  w[cur]:9;
	int ans = 0;
	for (int i = 0; i <= tp; i++)
	{
		ans += dfs(cur - 1, limit && (i == tp), sum + i);
	}
	return dp[cur][limit][sum] = (ans % mod);
}
int find(int x)
{
	w[0] = 0;
	while (x)w[++w[0]] = x % 10, x /= 10;
	memset(dp, -1, sizeof(dp));
	return dfs(w[0], 1, 0)%mod;
}
signed main()
{
	//ios::sync_with_stdio(false);
#ifndef ONLINE_JUDGE
	freopen("test.in", "r", stdin);
#endif // ONLINE_JUDGE
	read(t);
	
	while (t--)
	{
		int l, r;
		read(l), read(r);
		printf("%lld\n", ((find(r) - find(l-1)) % mod + mod)%mod);
	}
	return 0;
}

P4317 花神的数论题

题目链接:P4317 花神的数论题
题目大意:
在这里插入图片描述
数据范围: 1 ≤ N ≤ 1 0 15 1\le N\le10^{15} 1N1015
题解:我们考虑去计算 1 − N 1-N 1N s u m ( i ) = x sum(i)=x sum(i)=x的个数,然后累乘起来就行了。现在问题就是如何求出 s u m ( i ) = x sum(i)=x sum(i)=x i i i的个数。我们采取记忆化搜索(数位dp)的方法来进行。重要讲解一下 d f s dfs dfs函数,参数里面 c u r cur cur表示剩余位数, u p up up表示是否顶着上界, n o w now now表示现在已经遍历出来的1的个数, q u e que que表示要求的1的个数。
AC代码:

#include<bits/stdc++.h>

#define ld long double
#define ll long long
using namespace std;
template<class T>
void read(T& x)
{
	T res = 0, f = 1; char c = getchar();
	while (!isdigit(c)) {
		if (c == '-')f = -1; c = getchar();
	}
	while (isdigit(c)) {
		res = (res << 3) + (res << 1) + c - '0'; c = getchar();
	}
	x = res * f;
}
const ll N = 200000 + 10;
const int mod = 10000007;
#define int long long
ll fpow(int x, int y)
{
	int ans = 1;
	while (y)
	{
		if (y & 1)ans = 1ll * ans * x % mod;
		x = 1ll * x * x % mod; y >>= 1;
	}
	return ans;
}
int w[55];
int f[55][2][55][55];
int dfs(int cur, int up, int now, int que)
{
	if (!cur)
	{
		return now == que;
	}

	if (~f[cur][up][now][que])return f[cur][up][now][que];
	int tp = up ? w[cur] : 1;
	int ans = 0;
	for (int i = 0; i <= tp; i++)
	{
		ans = (ans + dfs(cur - 1, up && i == tp, now + (i == 1), que));
	}
	return f[cur][up][now][que] = ans;
}
signed main()
{
	//ios::sync_with_stdio(false);
#ifndef ONLINE_JUDGE
	freopen("test.in", "r", stdin);
#endif // ONLINE_JUDGE
	ll n;
	read(n);
	while (n)
		w[++w[0]] = n & 1, n >>= 1;
	ll ans = 1;
	memset(f, -1, sizeof(f));
	for (int i = 1; i <= w[0]; i++)
	{
		ans = ans * fpow(i, dfs(w[0], 1, 0, i)) % mod;
	}
	printf("%lld\n", ans);
	return 0;
}

P4127 [AHOI2009]同类分布

题目链接:P4127 [AHOI2009]同类分布
题目大意:
在这里插入图片描述
数据范围: 1 ≤ a ≤ b ≤ 1 0 18 1≤a≤b≤10^{18} 1ab1018
题解:很明显的数位dp,基本上数位dp都是要求 1 − n 1-n 1n中有多少个数满足xx要求,我们用 f i n d ( n ) find(n) find(n)函数来代替,这题答案就是 f i n d ( b ) − f i n d ( a − 1 ) find(b)-find(a-1) find(b)find(a1),我们考虑 f i n d ( n ) find(n) find(n)这个函数要怎么求,一般来说数位dp的搜索函数都要有这几个参数, c u r cur cur表示还剩多少位, l i m i t limit limit表示是否顶着上界,然后有几个参数用来判断是否满足条件和剪枝。这题里面 s u m sum sum表示搜索的数的数位和是多少, r e m rem rem表示搜索的数模 m o d mod mod后的值,其中 m o d mod mod表示我们要求的数位和。
AC代码:

#include<bits/stdc++.h>

#define ld long double
#define ll long long
using namespace std;
template<class T>
void read(T& x)
{
	T res = 0, f = 1; char c = getchar();
	while (!isdigit(c)) {
		if (c == '-')f = -1; c = getchar();
	}
	while (isdigit(c)) {
		res = (res << 3) + (res << 1) + c - '0'; c = getchar();
	}
	x = res * f;
}
const ll N = 200 + 10;
#define int long long
ll f[20][163][163], w[N];
int mod;
ll dfs(int cur, int sum, int rem, int limit)
{
	if (!cur)
	{
		return (mod == sum) && (rem == 0);
	}
	if (!limit && ~f[cur][sum][rem])return f[cur][sum][rem];
	if (sum > mod || sum + 9 * cur < mod)return 0;
	int tp = limit ? w[cur] : 9;
	ll ans = 0;
	for (int i = 0; i <= tp && sum + i <= mod; i++)
	{
		ans += dfs(cur - 1, sum + i, (rem * 10 + i) % mod, limit && (i == tp));
	}
	return f[cur][sum][rem] = ans;
}
ll find(ll x)
{
	w[0] = 0;
	while (x)
		w[++w[0]] = x % 10, x /= 10;
	ll ans = 0;
	for (mod = 1; mod <= w[0] * 9; mod++)
	{
		memset(f, -1, sizeof(f));
		ans += dfs(w[0], 0, 0, 1);
	}
	return ans;
}
signed main()
{
	//ios::sync_with_stdio(false);
#ifndef ONLINE_JUDGE
	freopen("test.in", "r", stdin);
#endif // ONLINE_JUDGE
	ll a, b;
	read(a), read(b);
	printf("%lld\n", find(b) - find(a - 1));

	return 0;
}

P4124 [CQOI2016]手机号码

题目链接:P4124 [CQOI2016]手机号码
题目大意:
在这里插入图片描述
数据范围: 1 0 10 ≤ L ≤ R ≤ 1 0 11 10^{10}\le L \le R \le10^{11} 1010LR1011
题解:数位dp感觉多写几题就能发现都是一个套路了,就是记忆化搜索,这里我们把所有要维护的东西都丢进参数里面。
AC代码:

#include<bits/stdc++.h>

#define ld long double
#define ll long long
using namespace std;
template<class T>
void read(T& x)
{
	T res = 0, f = 1; char c = getchar();
	while (!isdigit(c)) {
		if (c == '-')f = -1; c = getchar();
	}
	while (isdigit(c)) {
		res = (res << 3) + (res << 1) + c - '0'; c = getchar();
	}
	x = res * f;
}
#define int long long
const ll N = 200000 + 10;
const int mod = 1e9 + 7;
int dp[15][2][11][11][2][2][2];
int w[20];
int dfs(int cur, int limit, int pre1, int pre2, bool isok, bool _4, bool _8)
{
	if (_4 && _8)return 0;
	if (!cur)return isok;
	if (~dp[cur][limit][pre1][pre2][isok][_4][_8])return dp[cur][limit][pre1][pre2][isok][_4][_8];
	int tp = limit ? w[cur] : 9;
	int ans = 0;
	for (int i = 0; i <= tp; i++)
	{
		ans += dfs(cur - 1, limit && (i == tp), i, pre1, isok || (i == pre1 && i == pre2), _4 || (i == 4), _8 || (i == 8));
	}
	return dp[cur][limit][pre1][pre2][isok][_4][_8] = ans;
}
int find(int x)
{
	w[0] = 0;
	while (x)w[++w[0]] = x % 10, x /= 10;
	if (w[0] != 11)return 0;
	memset(dp, -1, sizeof(dp));
	int ans = 0;
	for (int i = 1; i <= w[w[0]]; i++)
	{
		ans += dfs(w[0] - 1, i == w[w[0]], i, 0, 0, i == 4, i == 8);
	}
	return ans;
}
signed main()
{
	//ios::sync_with_stdio(false);
#ifndef ONLINE_JUDGE
	freopen("test.in", "r", stdin);
#endif // ONLINE_JUDGE
	ll l, r;
	read(l), read(r);
	printf("%lld\n", find(r) - find(l - 1));

	return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值