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可以从观察性质的角度切入。

posted @ 2025-08-23 20:00  oymz  阅读(30)  评论(2)    收藏  举报