文章目录
前言
数位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}
0≤L≤R≤1019
题解:将数位使用的奇偶情况用一个数字
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}
1≤T≤50,1≤L≤R≤1018
题解:非常好的一道题,我也是参考题解才写出来的。我们先考虑一下如何求出与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
10cur∗10cur+2∗next∗10cur+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(AnAn−1An−2...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=1∑nAi∗2i−1。给定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
0≤T≤10000,0≤A,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
0≤x≤y≤1018,0<T≤30
题解:蛮有思维的数位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=right−left,这样数组就可以少一维,这样就完全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
1≤L<R≤2∗109
题解:数位
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
1≤T≤105,1≤N≤263−1
题解:我们可以发现,不同的数位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
1≤L≤R≤263−1,1≤K≤10
题解:我们用一个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<n≤m<1000000
题解:这个数据量不是很大,所以可以直接暴力
L
→
R
L\to R
L→R扫一遍,看是否有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]−[1−L−1]。然后用
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}
1≤t≤10,1≤l≤r≤9∗1018
题解:非常好的一道题,数位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} 9∗1018的大小!空间已经炸穿了。我们这里可以发现一个空间小优化就是我们可以预处理出 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! 19∗2∗9!∗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 19∗2∗2520∗2520。还是超了一点。。。不过我们可以发现 p r e l c m prelcm prelcm无法取到 1 → 2520 1\to 2520 1→2520之间的所有数,只能取到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 19∗2∗50∗2520,完全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
1≤n≤109
题解:很简单的数位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
1≤L≤R≤1018,1≤T≤20
题解:数位
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}
1≤N≤1015
题解:我们考虑去计算
1
−
N
1-N
1−N中
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}
1≤a≤b≤1018
题解:很明显的数位dp,基本上数位dp都是要求
1
−
n
1-n
1−n中有多少个数满足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(a−1),我们考虑
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}
1010≤L≤R≤1011
题解:数位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;
}