动态规划_数位DP

时间:2022-12-15 22:47:20

数位DP的一般思路

当计算一个很大的数中,符合条件的数有多少种,一般采取数位DP的方式去解决该问题。作为模板,我采用了记忆华搜索的方法。

  • 解题思路
  • 代码框架
  • 例题1 HDU 2089(简单)
  • 例题2 HDU 4507(适中)

解题思路

对于这类问题,我们将考虑数字每位上的情况。
对于数字5236,第四位上的数字,0~4可归为一种情况,5可归为一种情况,我们称5为Limited(受限制)的情况。对于前者,我们可以对次一位进行任意的排列组合(即次一位可选0~9),而后者,只能从零到次一位的最大数(对于刚才讨论的情况是0~2)。
那么,基于代码框架的角度来看,我们只需要分情况讨论即可。
由于为限制的情况我们可能会多次搜索到,所以我们采用记忆化搜索来解决重复计算的问题。


代码框架

以深度优先搜索的记忆华搜索框架:

/*
dp[len][..]表示第len位在未限制情况下的符合要求的个数。
..表示题目可能要求的条件
*/

Type dp[maxLen][];
/*
@input: len 数位
@input: isLimited 是否为受限制情况
@input: others 题目可能出现的要求
*/

Type dfs(int len, int isLimited, ..) {
if(len == 0) return 1;
Type ans = 0;
if(isLimited) {
//TODO
//受限制情况的讨论
}
else {
//TODO
//不受情况的讨论
}
return isLimited?ans:dp[0][len][isSix]=ans ;//记忆化搜索
}

有时可以将一些相似的情况归并在一起计算(如例题2的代码)。

除了深度优先搜索外,我们还需要将数字的每一位进行分解,如下:

int solve(int n) {
int len = 1;
while(n) {
str[len++] = n % 10;
n /= 10;
}
return dfs(len-1, Limited, ..);
}

例题1 [ HDU 2089 不要62]

题目很简单,不需要过多解释,直接贴代码。

#include<cstdio>
#include<cstring>
const int maxLen = 16;
int str[maxLen];
int dp[maxLen][2];

int dfs(int len, int isLimited, int isSix) {
if(len == 0) return 1;
int ans = 0;
if(isLimited) {
for(int i=0; i<=str[len]; i++) {//0~str[len]
if(i==4 || i==2 && isSix) continue;
ans += dfs(len - 1, i==str[len], i==6);
}
}
else {
if(dp[len][isSix]>=0)
return dp[len][isSix];
ans += (8-isSix) * dfs(len - 1, 0, 0);//0 1 3 5 7 8 9 | 2
ans += dfs(len - 1, 0, 1);//6
}
return isLimited?ans:dp[len][isSix]=ans ;
}

int solve(int n) {
int len = 1;
while(n) {
str[len++] = n % 10;
n /= 10;
}
return dfs(len-1, true, false);
}

int main() {
int n, m;
memset(dp, -1, sizeof(dp));
while (~scanf("%d%d", &n, &m) && (n || m)) {
printf("%d\n",(solve(m) - solve(n - 1)));
}
return 0;
}

例题2 [ HDU 4507 吉哥系列故事——恨7不成妻]

题目较为困难,但对于该框架来说,多想了几步而已。

TIPS
1. 考虑(A1+b) + (A2+b) + (A3+b) + ··· + (Acount+b) 与 A1 + A2 + A3 + ··· + Acount 的关系

2.考虑 (A1+b)2 + (A2+b)2 + (A3+b)2 + ··· + (Acount+b)2 与 A12 + A22 + A32 + ··· + Acount2 的关系

附AC代码

#include<cstdio>
#include<cstring>
typedef __int64 ll;
const int maxLen = 32;
const ll MOD = 1e9 + 7;
int str[maxLen];
ll powTen[maxLen];

struct node {
ll sum;
ll cnt;
ll ans;
node() { this->sum = 0; this->cnt = -1; this->ans = 0; }
node(ll sum, ll cnt, ll ans) { this->sum = sum; this->cnt = cnt; this->ans = ans; }
};

node dp[maxLen][10][10];

node dfs(int len, int isLimited, int sum, int mod) {
if(len == 0) {
return node(0,!(sum%7==0 || mod ==0),0);
}

if(dp[len][sum][mod].cnt>=0 && !isLimited)
return dp[len][sum][mod];

node x = node(0, 0, 0);
int maxBit = isLimited?str[len]:9;

for(int i=0; i<=maxBit; i++) {
if(i==7) continue;
ll base = powTen[len] * i % MOD;
node bff = dfs(len-1, isLimited && i==maxBit, (sum+i)%7, (mod*10+i)%7);
(x.cnt += bff.cnt) %= MOD;
(x.sum += bff.sum + bff.cnt * base % MOD) %= MOD;
(x.ans += bff.ans + 2 * base * bff.sum % MOD + bff.cnt * (base * base % MOD) % MOD) %= MOD;
}
return isLimited ? x : dp[len][sum][mod]=x;
}

ll solve(ll n) {
int len = 1;
while(n>0) {
str[len++] = n % 10;
n /= 10;
}
return dfs(len-1, true, 0, 0).ans;
}

int main() {

ll n, m;
// { init
powTen[1] = 1;
for(int i=2; i<maxLen; i++) {
powTen[i] = powTen[i-1] * 10 % MOD;
}
// }
int T;
scanf("%d", &T);
while (T--) {
scanf("%I64d%I64d", &n, &m);
printf("%I64d\n",(solve(m) + MOD - solve(n - 1))%MOD);
}
return 0;
}