数位DP
数位DP通常适用于以下问题:
给定一个区间 [ l , r ] [l,r] [l,r],给定一个条件函数 C ( n ) C(n) C(n),若数 n n n满足条件 C C C,则 C ( n ) C(n) C(n)的值为 1 1 1,否则为 0 0 0。求在这个区间满足条件 C C C的数的个数有几个?
数位DP,顾名思义,就是在数的个位,十分位,百分位,……上做DP。
我们定义:
d p ( p o s , p r e , l i m i t ) dp(pos,pre,limit) dp(pos,pre,limit)表示从低位开始算起,计算第 p o s pos pos位的值符合条件的数的数量。
其中:
p r e pre pre表示 p o s + 1 pos+1 pos+1位上的数, l i m i t limit limit表示到 p o s + 1 pos+1 pos+1位为止,是否存在限制。
这里的限制是指,假设枚举小于 2345 2345 2345的四位数,枚举到第 4 4 4为,此时第四位上的数可以为 0 , 1 , 2 0,1,2 0,1,2,因为必须小于 2345 2345 2345所以小于的限制没有解除。如果第四位为2,那么第三位的上限为 3 3 3而不是 9 9 9,因为小于的限制仍然没有解除。如果第四位为 1 1 1,那么后面的数位的上限都是 9 9 9,因为小于的限制已经解除了,这就是 l i m i t limit limit状态的作用了。
模板例题
#include <bits/stdc++.h>
using namespace std;
#define FR freopen("in.txt", "r", stdin)
#define FW freopen("out.txt", "w", stdout)
typedef long long ll;
int arr[10];
int dp[10][2][2];
// dp[pos][withTwo][limit]
// dp[满足第0-pos位][pos位计数时带不带2][有没有限制]
int dfs(int pos, bool withTwo, bool limit)
{
// 如果到了末尾返回1
if (pos == -1)
return 1;
// 如果有记录直接查表
if (dp[pos][withTwo][limit] != -1)
{
return dp[pos][withTwo][limit];
}
// 计算上限
int up = limit ? arr[pos] : 9;
int cnt = 0;
// 枚举数位
for (int i = 0; i <= up; i++)
{
// 如果该位不带2,但是遍历到2,或者遍历到4,结束当前遍历
if ((!withTwo && i == 2) || i == 4)
continue;
// 求到[0,pos-1]为止,带不带2由i != 6决定,限制为固定写法
cnt += dfs(pos - 1, i != 6, limit && i == arr[pos]);
}
dp[pos][withTwo][limit] = cnt;
return cnt;
}
// 前缀和函数,求满足[0,up]区间内的数字的个数
int psum(int up)
{
memset(dp, -1, 40 * sizeof(int));
int pos = 0;
while (up > 0)
{
arr[pos++] = up % 10;
up /= 10;
}
return dfs(pos - 1, true, true);
}
int main()
{
int l, r;
for (;;)
{
scanf("%d %d", &l, &r);
if (l == 0 && r == 0)
break;
// 前缀和做减法
printf("%d\n", psum(r) - psum(l - 1));
}
return 0;
}
#include <bits/stdc++.h>
#define FR freopen("in.txt", "r", stdin)
using namespace std;
typedef long long ll;
ll dp[15][2][15][2];
ll acc[15][2];
ll arr[15];
ll dfs2(int pos, int limit)
{
if (pos == -1)
return 1;
if (acc[pos][limit] != -1)
{
return acc[pos][limit];
}
int upper = limit ? arr[pos] : 9;
ll ans = 0;
for (int i = 0; i <= upper; i++)
{
ans += dfs2(pos - 1, limit && i == upper);
}
return acc[pos][limit] = ans;
}
ll dfs(int pos, int limit, int bit, int leader)
{
if (pos == -1)
return 0;
if (dp[pos][limit][bit][leader] != -1)
{
return dp[pos][limit][bit][leader];
}
int upper = limit ? arr[pos] : 9;
ll ans = 0;
for (int i = 0; i <= upper; i++)
{
ans += dfs(pos - 1, limit && i == upper, bit, leader && i == 0);
if (i != 0 || !leader)
{
ans += (i == bit) * (pos != 0 ? acc[pos - 1][limit && i == upper] : 1);
}
}
return dp[pos][limit][bit][leader] = ans;
}
ll psum(ll x, int bit)
{
memset(dp, -1, sizeof(dp));
memset(acc, -1, sizeof(acc));
int i = 0;
for (; x; x /= 10, i++)
{
arr[i] = x % 10;
}
dfs2(i - 1, true);
return dfs(i - 1, 1, bit, 1);
}
int main()
{
ll a, b;
scanf("%lld %lld", &a, &b);
for (int bit = 0; bit <= 9; bit++)
{
printf("%lld ", psum(b, bit) - psum(a - 1, bit));
}
return 0;
}