• 博客园logo
  • 会员
  • 周边
  • 新闻
  • 博问
  • 闪存
  • 赞助商
  • Chat2DB
    • 搜索
      所有博客
    • 搜索
      当前博客
  • 写随笔 我的博客 短消息 简洁模式
    用户头像
    我的博客 我的园子 账号设置 会员中心 简洁模式 ... 退出登录
    注册 登录
PCMSFV
博客园    首页    新随笔    联系   管理    订阅  订阅

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}\) 为:

\[\text{val} = \sum_{i=1}^{k} \binom{L_i}{2} = \sum_{i=1}^{k} \frac{L_i(L_i - 1)}{2} \]

统计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段结束] = 可以达到上述要求的情况数。

也就是如下图这样:

image-20260613181919444

看图可以发现,标绿部分的贡献可以算出,为:(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),这样第一步也能用同一个公式,不用单独写特殊处理。

这三个思路跳出这道题照样能用,以后遇到“分段 + 累积代价 + 相邻有约束”的问题,可以尝试这些思路。

posted on 2026-06-13 21:42  PCMSFV  阅读(7)  评论(0)    收藏  举报
刷新页面返回顶部
博客园  ©  2004-2026
浙公网安备 33010602011771号 浙ICP备2021040463号-3