您的位置:首页 > 其它

[bzoj 1833] [ZJOI2010]count 数字计数:数位DP

2016-11-07 16:02 555 查看
题意:求[a, b]之间的所有整数中各个数码出现了多少次。(1<=a<=b<=10^12,a, b是整数)

考虑[1, x)内各个数码出现了多少次。设
f[i][j][k]
为以j开头的(i+1)位十进制串中k出现的次数,递推。

[1, x)内的数可分为三类:

1. 位数少于x。

2. 最高位小于x。

3. 和x有公共的前缀。

分类统计,第二、三类可以合并。设现在考虑从高往低第k位,则
f
数组包含k及其右边的数码,k左边的数码还需要单独计算。

看了Po姐的题解,感觉比我写的要美……

f[i][j][k]
可简化为
f[i]
(依然允许前导0),因为每个数码出现的次数相同——只是一些字符串罢了。有递推式
f[i] = f[i-1]*10 + 10^i
,其中
f[i-1]*10
是低i位中某数码的出现次数(用0~9共10个数码作为最高位),
10^i
是它在最高位的出现次数。

位数少于x的数,枚举1~9作为最高位。最高位中每个数码出现
10^i
次,低i位中每个数码出现
f[i-1]*9
次。

和x有公共前缀的数,自由的非公共部分每个数码出现
f[i-1]
次,公共部分和非自由非公共部分一起暴力统计。公共部分可以每次计算,也可以像我的代码这样放在一起考虑,如x=1234,则1在公共部分出现234次,2在公共部分出现34次,3在公共部分出现4次。

于是又写了一个版本。

#include <cstdio>
#include <cstring>
using namespace std;
const int MAX_N = 13;
typedef long long ll;

// f[i][j][k]: 以j打头的(i+1)位十进制串含多少个k
// g[i][j] = Sigma(f[i][*][j])
ll p[MAX_N], f[MAX_N][10][10], g[MAX_N][10], v[2][10];

void cal(ll x, ll a[10])
{
ll b[MAX_N];
int n = 0;
for (ll y = x; y; y /= 10)
b[n++] = y % 10;
for (int i = 0; i < n-1; ++i)
for (int j = 0; j < 10; ++j)
a[j] += g[i][j] - f[i][0][j];
for (int i = n-1; i >= 0; --i)
for (int j = (i == n-1); j < b[i]; ++j)
for (int k = 0; k < 10; ++k)
a[k] += f[i][j][k];
for (ll i = 1, y = b[0]; i < n; y = y + b[i]*p[i], ++i)
a[b[i]] += y;
}

int main()
{
ll a, b;
scanf("%lld %lld", &a, &b);
for (int i = 0; i < 10; ++i)
f[0][i][i] = g[0][i] = 1;
p[0] = 1;
for (int i = 1; i < MAX_N; ++i) {
p[i] = p[i-1]*10;
for (int j = 0; j < 10; ++j) {
f[i][j][j] = p[i];
for (int k = 0; k < 10; ++k) {
f[i][j][k] += g[i-1][k];
g[i][k] += f[i][j][k];
}
}
}
cal(b+1, v[0]);
cal(a, v[1]);
for (int i = 0; i < 10; ++i)
printf("%lld%c", v[0][i]-v[1][i], " \n"[i == 9]);
return 0;
}


#include <cstdio>
using namespace std;
typedef long long ll;
const int MAX_N = 13;
ll f[MAX_N], p[MAX_N], v[2][10];

void cal(ll x, ll a[10])
{
ll b[MAX_N];
int n = 0;
for (ll y = x; y; y /= 10)
b[n++] = y % 10;
for (int i = 0; i < n-1; ++i)
for (int j = 0; j < 10; ++j)
a[j] += (j ? p[i] : 0) + (i ? f[i-1]*9 : 0); // 最高位,低i位
for (int i = n-1, c; c = 0, i >= 0; --i) {
for (int j = (i==n-1); j < b[i]; ++c, ++j)
a[j] += p[i];
for (int j = 0; j < 10; ++j)
a[j] += i ? c*f[i-1] : 0;
}
for (ll i = 1, y = b[0]; i < n; y += p[i]*b[i], ++i)
a[b[i]] += y;
}

int main()
{
ll a, b;
scanf("%lld %lld", &a, &b);
p[0] = f[0] = 1;
for (int i = 1; i < MAX_N; ++i) {
p[i] = p[i-1]*10;
f[i] = f[i-1]*10 + p[i];
}
cal(b+1, v[0]);
cal(a, v[1]);
for (int i = 0; i < 10; ++i)
printf("%lld%c", v[0][i] - v[1][i], " \n"[i == 9]);
return 0;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  动态规划 数位dp