DP - 数位DP -数字游戏(Ⅰ+Ⅱ)
1、数字游戏Ⅰ
科协里最近很流行数字游戏。
某人命名了一种不降数,这种数字必须满足从左到右各位数字呈非下降关系,如 123,446。
现在大家决定玩一个游戏,指定一个整数闭区间 [a,b],问这个区间内有多少个不降数。
输入格式
输入包含多组测试数据。
每组数据占一行,包含两个整数 a 和 b。
输出格式
每行给出一组测试数据的答案,即 [a,b] 之间有多少不降数。
数据范围
1≤a≤b≤231−1
输入样例:
1 9
1 19
输出样例:
9
18
分析:
d p 函 数 求 1 dp函数求1 dp函数求1~ N 中 不 降 数 的 个 数 。 N中不降数的个数。 N中不降数的个数。
首 先 将 N 每 一 位 存 入 数 组 V = a n − 1 a n − 2 . . . a 0 , 首先将N每一位存入数组V=a_{n-1}a_{n-2}...a_{0}, 首先将N每一位存入数组V=an−1an−2...a0,
从 高 位 到 低 位 [ n − 1 , 0 ] 依 次 遍 历 : 从高位到低位[n-1,0]依次遍历: 从高位到低位[n−1,0]依次遍历:
设
f
[
i
,
j
]
表
示
长
度
为
i
,
且
最
高
位
为
j
的
数
内
,
不
降
数
的
个
数
。
设f[i,j]表示长度为i,且最高位为j的数内,不降数的个数。
设f[i,j]表示长度为i,且最高位为j的数内,不降数的个数。
则
f
[
i
,
j
]
+
=
f
[
i
−
1
,
k
]
,
其
中
k
>
=
j
。
这
里
得
到
f
数
组
的
转
移
方
程
。
则f[i,j]+=f[i-1,k],其中k>=j。这里得到f数组的转移方程。
则f[i,j]+=f[i−1,k],其中k>=j。这里得到f数组的转移方程。
l
a
s
t
表
示
上
一
位
数
的
大
小
。
last表示上一位数的大小。
last表示上一位数的大小。
考 虑 第 i 位 数 a i 考虑第i位数a_i 考虑第i位数ai
① 、 若 l a s t < = a i < V i , 则 无 论 a i a i − 1 . . . a 0 取 何 值 , 均 不 会 超 过 V , 总 方 案 数 r e s + = f [ i + 1 ] [ j ] 。 ①、若last<=a_i<V_i,则无论a_{i}a_{i-1}...a_0取何值,均不会超过V,总方案数res+=f[i+1][j]。 ①、若last<=ai<Vi,则无论aiai−1...a0取何值,均不会超过V,总方案数res+=f[i+1][j]。
② 、 若 l a s t > V i , 此 时 无 论 如 何 都 无 法 选 出 合 法 方 案 , 直 接 退 出 循 环 , 否 则 更 新 l a s t = V i 。 ②、若last>V_i,此时无论如何都无法选出合法方案,直接退出循环,否则更新last=V_i。 ②、若last>Vi,此时无论如何都无法选出合法方案,直接退出循环,否则更新last=Vi。
③ 、 若 i = 0 , 即 考 虑 到 最 后 一 位 a 0 , 还 要 加 上 V 本 身 这 个 方 案 。 ③、若i=0,即考虑到最后一位a_0,还要加上V本身这个方案。 ③、若i=0,即考虑到最后一位a0,还要加上V本身这个方案。
代码:
#include<iostream>
#include<vector>
using namespace std;
const int N=15;
int f[N][N];
void cal()
{
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=j;k<=9;k++)
f[i][j]+=f[i-1][k];
}
int dp(int n)
{
if(!n) return 1;
vector<int> V;
while(n) V.push_back(n%10),n/=10;
int res=0,last=0;
for(int i=V.size()-1;i>=0;i--)
{
int x=V[i];
for(int j=last;j<x;j++)
res+=f[i+1][j];
if(x<last) break;
last=x;
if(!i) res++;
}
return res;
}
int main()
{
cal();
int l,r;
while(cin>>l>>r) cout<<dp(r)-dp(l-1)<<endl;
return 0;
}
2、数字游戏Ⅱ
由于科协里最近真的很流行数字游戏。
某人又命名了一种取模数,这种数字必须满足各位数字之和 mod P 为 0。
现在大家又要玩游戏了,指定一个整数闭区间 [a.b],问这个区间内有多少个取模数。
输入格式
输入包含多组测试数据,每组数据占一行。
每组数据包含三个整数 a,b,P。
输出格式
对于每个测试数据输出一行结果,表示区间内各位数字和 mod P 为 0 的数的个数。
数据范围
1≤a,b≤231−1,
1≤P<100
输入样例:
1 19 9
输出样例:
2
分析:
与 第 一 题 类 似 的 , 与第一题类似的, 与第一题类似的,
把 N 的 每 一 位 存 入 数 组 , 把N的每一位存入数组, 把N的每一位存入数组,
从 高 位 到 低 位 依 次 遍 历 每 一 位 数 , 从高位到低位依次遍历每一位数, 从高位到低位依次遍历每一位数,
设 f [ i , j , k ] 表 示 长 度 为 i , 末 尾 数 字 为 j , 且 各 位 数 字 之 和 对 P 取 模 的 余 数 是 k 的 合 法 方 案 总 数 。 设f[i,j,k]表示长度为i,末尾数字为j,且各位数字之和对P取模的余数是k的 合法方案总数。 设f[i,j,k]表示长度为i,末尾数字为j,且各位数字之和对P取模的余数是k的合法方案总数。
形
如
:
形如:
形如:
由
(
j
+
x
+
S
)
%
P
=
k
得
(
x
+
S
)
%
P
=
(
k
−
j
)
%
P
,
则
f
[
i
]
[
j
]
[
k
]
可
由
f
[
i
−
1
]
[
x
]
[
(
k
−
j
)
%
P
]
转
移
而
来
。
由(j+x+S)\%P=k得(x+S)\%P=(k-j)\%P,则f[i][j][k]可由f[i-1][x][(k-j)\%P]转移而来。
由(j+x+S)%P=k得(x+S)%P=(k−j)%P,则f[i][j][k]可由f[i−1][x][(k−j)%P]转移而来。
即 f [ i ] [ j ] [ k ] + = f [ i − 1 ] [ x ] [ ( k − j ) % P ] , 求 f 数 组 的 转 移 方 程 。 即f[i][j][k]+=f[i-1][x][(k-j)\%P],求f数组的转移方程。 即f[i][j][k]+=f[i−1][x][(k−j)%P],求f数组的转移方程。
注意: 负 数 取 模 。 负数取模。 负数取模。
设 l a s t = V n − 1 + V n − 2 + . . . + V i + 1 , 第 i 位 数 填 j 设last=V_{n-1}+V_{n-2}+...+V_{i+1},第i位数填j 设last=Vn−1+Vn−2+...+Vi+1,第i位数填j
① 、 若 j < V i , 无 论 S a = a i a i − 1 . . . a 0 这 i + 1 位 填 什 么 数 , 都 不 会 超 过 V 。 又 因 为 需 满 足 总 和 ( S a + l a s t ) % P = 0 , 故 S a 对 P 取 模 的 余 数 应 当 是 ( − l a s t ) % P 。 总 方 案 数 r e s + = f [ i + 1 ] [ j ] [ ( − l a s t ) % P ] 。 ①、若j<V_i,无论S_a=a_{i}a_{i-1}...a_{0}这i+1位填什么数,都不会超过V。\\\qquad又因为需满足总和(S_a+last)\%P=0,故S_a对P取模的余数应当是(-last)\%P。\\\qquad总方案数res+=f[i+1][j][(-last)\%P]。 ①、若j<Vi,无论Sa=aiai−1...a0这i+1位填什么数,都不会超过V。又因为需满足总和(Sa+last)%P=0,故Sa对P取模的余数应当是(−last)%P。总方案数res+=f[i+1][j][(−last)%P]。
② 、 更 新 l a s t , 若 枚 举 到 最 后 一 位 a 0 , 且 总 和 l a s t % P = 0 , 还 要 加 上 V 本 身 这 一 个 方 案 。 ②、更新last,若枚举到最后一位a_0,且总和last\%P=0,还要加上V本身这一个方案。 ②、更新last,若枚举到最后一位a0,且总和last%P=0,还要加上V本身这一个方案。
代码:
#include<iostream>
#include<vector>
#include<cstring>
using namespace std;
const int N = 11;
int f[N][N][110];
int l,r,P;
int mod(int x,int p)
{
return (x%p+p)%p;
}
void cal()
{
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> V;
while(n) V.push_back(n%10),n/=10;
int res=0,last=0;
for(int i=V.size()-1;i>=0;i--)
{
int x=V[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)
{
cal();
cout<<dp(r)-dp(l-1)<<endl;
}
return 0;
}