NC305811 轮符雨
题意
有一个长度为n的01串,按照极大连续同字符段(L)分解。
设这些 \(L\) 的长度依次为 \(L_1, L_2, \ldots, L_k\)(\(L_i \ge 1\),且 \(L_1+L_2+\cdots+L_k=n\))。
定义该字符串的值 \(\mathrm{val}\) 为:
统计val = t的字符串数量,结果对1e9 + 7取模。
数据范围
每个测试文件均包含多组测试数据。第一行输入一个整数 T (1≤T≤20)代表数据组数。
每组测试数据在一行上输入两个整数 n和 t,其取值范围满足:
\(1\leq n\leq 100\) ,\(0\leq t\leq \dfrac{n(n-1)}{2}\)
保证单个测试文件的 n之和不超过 100。
解题思路
看到n的范围,暴力会超时,但对于其他算法n又会显得有点小。
题目将01串分成一段一段的形式,且每一段(\(L_i\))不是全0就是全1(也就是极大连续同字符段的意思),每一段都有自己的贡献。
val就是由一个一个互相隔开,不相交的极大连续同字符段,贡献得来。(贡献基于段,而不是字符)
可以发现,\(L_i\)是独立贡献给val的,和其他的极大连续同字符段无关。
这就满足了无后效性,于是使用DP。
DP长这样:dp[i:长度,也可以理解为1~i的末位下标][j:这一整段的总贡献][0/1:最后一段是由0/1段结束] = 可以达到上述要求的情况数。
也就是如下图这样:

看图可以发现,标绿部分的贡献可以算出,为:(i - k) * (i - k - 1) / 2(套公式)。
令add = (i - k) * (i - k - 1) / 2
那白色部分的贡献,就是j - add
而且白色部分,一定是由1结尾的,不然就到绿色部分了(极大连续同字符段的定义)。
于是得到,dp[i][j][0] = (dp[i][j][0] + dp[k][j - add][1]) % P。
dp[i][j][1]的转移也是同理。
答案是(dp[n][t][0] + dp[n][t][1]) % P很好理解。
但是其实到这里没有结束,先贴代码:
#include <bits/stdc++.h>
using namespace std;
constexpr int N = 1e2 + 2;
constexpr int P = 1e9 + 7;
int dp[N][N * N][2];
void solve() {
memset(dp, 0, sizeof dp); // 多测万万要清零!
int n, t;
cin >> n >> t;
dp[0][0][0] = dp[0][0][1] = 1; // Q1:这是啥?
for (int i = 1; i <= n; i++) {
for (int j = 0; j <= i * (i - 1) / 2; j++) { // Q2:从0?
for (int k = 0; k < i; k++) { // Q3:从0?
int add = (i - k) * (i - k - 1) / 2;
if (j < add) continue; // Q4:还会有这种情况?
dp[i][j][0] = (dp[i][j][0] + dp[k][j - add][1]) % P;
dp[i][j][1] = (dp[i][j][1] + dp[k][j - add][0]) % P;
}
}
}
cout << (dp[n][t][0] + dp[n][t][1]) % P << '\n'; // 别把输出写到循环内部!
}
signed main() {
int T;
cin >> T;
while (T--) {
solve();
}
return 0;
}
Q1
这是用来处理,当前段只有一个极大连续同字符段的情况,且前面什么都没有(整个01串的开头)。
对于这些段,只会增加一种情况(这种字串就是它自己),也就是当j = add且k = 0时。
Q2
当然有0,贡献是0的字串例如:0101。
Q3
[0, i]中只有一段极大连续同字符段的情况,也就是需要+1的情况。
Q4
为了避免最后一段的贡献太大,即使前缀贡献为零也无法达到j的情况。
如果不避免,不仅逻辑错误,而且会导致数组越界。
难点
想到用DP解决。
设计DP的状态,转移方程反而相对简单。
总结
1.不要总是想把一个 字符串/数组 拆成一个一个 字符/数字 解决,发现可以分段,可以尝试把一段看作一个整体。
2.对于段与段之间,界限分明,不相交也不隔开的,处理数组较小时,可以尝试DP。
3.可以把一个要处理的段分成自己和非己部分,比如[0, k]的所有可能的情况,[k + 1, i]这一段视做一个极大连续同字符段。
4.对于只有 0/1 这样状态少的情况,可以尝试给DP数组多加一维。
对以后做题的启发
1.如果下一步怎么做取决于上一步是什么(比如字符、颜色、方向),那状态里就要记下这个“上一步是什么”。
2.既然对象是由一段一段组成的,那递推的时候就直接枚举最后一段有多长,从剩下的部分转移过来,这样最省事。
3.别忘了给“什么都没开始”的状态设个初始值(比如 dp[0][0]=1),这样第一步也能用同一个公式,不用单独写特殊处理。
这三个思路跳出这道题照样能用,以后遇到“分段 + 累积代价 + 相邻有约束”的问题,可以尝试这些思路。
浙公网安备 33010602011771号