20250823 XYD 001 T2
题意
给你一个数列,请你求出将它分成若干个 \((x, x, x)\) 或 \((x, x + 1, x + 2)\) 的形式的方案数。
两个方案不同当且仅当三元组不能一一对应。
思路
方案数,所以考虑 DP。
首先可以想到两种最暴力的 DP,对于每个人考虑他去哪个组,对于每个组考虑他选那些人。第一种显然很不优,所以直接考虑第二种。但第二种需要将每个人是否被选作为状态,在转移时需要还要对于两种组的转移选三个点,所以考虑优化。
可以发现,三元组的限制都在值域上,所以显然可以在值域上考虑(大体是将每个值还剩多少个作为状态),又可以发现,每个三元组考虑的是一段连续的值域,所以可以将状态缩减为连续式三个值的剩余数量。又因为题目要求不计排列,所以可以按值域从小到大考虑且钦定前面的都是零(考虑的值域连续,使得不能在后面考虑前面),且在同一段值域时,钦定一个一种三元组和二种三元组的次序。由于我们观察到,一种三元组只关心 \(x\) 的数量,而二种三元组则需关心 \(x, x + 1, x + 2\) 的数量,所以考虑先组一种三元组再组二种三元组,这样就可以少记 \(x + 2\) 的数量这一维。由于不关心具体组的数量,所以对一种三元组考虑完全背包,而对于二种三元组,为了保证次序,应每次直接向下一层转移。
Code
#include<bits/stdc++.h>
using namespace std;
const int N = 5e3 + 5, MOD = 1e9 + 7;
int n, m;
int a[N], f[N], dp[2][N][N];
int main() {
ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
cin >> n >> m;
for (int i = 1; i <= n; i++) {
cin >> a[i];
f[a[i]]++;
}
dp[0][f[1]][f[0]] = 1;
for (int i = 2; i <= m + 2; i++) {//值域
for (int j = f[i - 1]; j >= 0; j--) {
for (int k = f[i - 2]; k >= 0; k--) {
if (k >= 3) dp[0][j][k - 3] = (dp[0][j][k - 3] + dp[0][j][k]) % MOD;//(x, x, x)
if (f[i] >= k && j >= k) dp[1][f[i] - k][j - k] = (dp[1][f[i] - k][j - k] + dp[0][j][k]) % MOD;//(x, x + 1, x + 2)
}
}
for (int j = max(f[i], f[i - 1]); j >= 0; j--) {//滚动
for (int k = max(f[i - 1], f[i - 2]); k >= 0; k--) {
dp[0][j][k] = dp[1][j][k], dp[1][j][k] = 0;
}
}
}
cout << dp[0][0][0];
return 0;
}
总结
DP 可以从暴力开始考虑。
要由暴力改DP可以从观察性质的角度切入。

浙公网安备 33010602011771号