AtCoder Beginner Contest 简要题解
ABC465
E
赛时考虑到了数位 dp,但是把它当一般的数位 dp 处理,这样做应该也是可以的,但是太过于麻烦。原因在于题目有 3 个限制,且只要满足其中一个,如果直接在算每一位时就考虑最终方案数,那么相当于要做 3 类数位 dp,且限制很强(因为还不能有重叠)。
但其实发现,“恰好满足一个限制”可以先转化为“至少满足一个限制”,对于这个进行数位 dp,最终算答案时就只统计所有“恰好满足一个限制”的方案数。
考虑状态设计,其实就是看最终算答案时的判定条件:
- 判 3 的倍数:记录位数和模 3 的值。
- 包含 3:记录出现数字的集合。
- 恰好含有 3 个不同的数字:同样记录出现数字集合。
于是设 \(dp(i,j,k,0/1)\) 为前 \(i\) 位,出现数字的集合为 \(j\),位数和模 3 的值为 \(k\),且当前是 \((1)\) 否 \((0)\) 达到上界。
刷表转移枚举下一位上的数字 \(d\),注意 \(d\) 的范围是 \([0,9]\)。
- 从非上界转移:\(dp(i,j,k,0)\to dp(i+1,j',k',0)\)
- 若 \(d\le lim\),则可以从上界转移:
- \(d < lim\),\(dp(i,j,k,1)\to dp(i+1,j',k',0)\)
- \(d = lim\),\(dp(i,j,k,1)\to dp(i+1,j',k',1)\)
初始化 \(dp(0,0,0,0)=1\)。
F
赛时想到一些容斥,但还是不会。
其实这题可以算是一道融合高维前缀和与高维差分的好~板题。
首先,前缀和最本质的求法就是逐维计算,也就是每次选择一个维度,其他维度固定,让该维度滚动更新。
比如,3 维前缀和的计算:
// 1th dimension
for (int i = 1; i <= n; i ++)
for (int j = 1; j <= n; j ++)
for (int k = 1; k <= n; k ++)
f[i][j][k] += f[i - 1][j][k];
// 2th dimension
for (int i = 1; i <= n; i ++)
for (int j = 1; j <= n; j ++)
for (int k = 1; k <= n; k ++)
f[i][j][k] += f[i][j - 1][k];
// 3th dimension
for (int i = 1; i <= n; i ++)
for (int j = 1; j <= n; j ++)
for (int k = 1; k <= n; k ++)
f[i][j][k] += f[i][j][k - 1];
那么对于本题就直接把每个字符串看作一个 6 位的 10 进制数,然后做一个 6 维的前缀和。当然不用真的建立 6 维,像 sos dp 那样枚举所有数(其实是状态压缩)看每一位转移就行。
接下来考虑询问。
高维差分我们考虑做一个容斥原理,把条件 \(x_i\le s_i\le y_i\) 拆成 \((满足 s_i\le y_i 的价值和)-(满足 s_i\le x_i-1 的价值和)\)。所以我们枚举限制状态,其中第 \(i\) 位上为 0,表示该位的限制为 \(s_i\le y_i\),否则为 \(s_i\le x_i\)。那么容斥系数为 \((-1)^{状态中 1 的个数}\)。
这里贴一下代码方便理解:
ll ans = 0;
for (int i = 0; i < (1 << 6); i ++) {
int num = 0;
bool flag = true;
for (int j = 0; j < 6; j ++) {
if (i >> j & 1) {
if (x[j] == '0') {flag = false; break;}
num = num * 10 + x[j] - '1';
}
else num = num * 10 + y[j] - '0';
}
if (flag) {
int w = __builtin_parity(i) ? -1 : 1;
ans += w * sum[num];
}
}

浙公网安备 33010602011771号