题解:P7213 [JOISC 2020] 最古の遺跡 3

需要一些观察的计数。

upd on 2025.11.19:\(g\) 的转移写少了一个系数。

题意:有一个由 \(1,2,\cdots n\) 各出现两次构成的序列 \(a\),对其进行 \(n\) 次操作,如果一个元素满足后面的元素没有和他相等的,那么他就不变,否则该元素减 \(1\),减到 \(0\) 的元素移除。现在给出最后剩下的元素在原序列中的下标,求原序列有多少种。

做法:

首先先进行一个简单的转化,我们先给每种数内部定序,这样的好处是我们可以直接变成一个 \(2n\) 长序列而不用考虑一种数会不会放多次,最后计算的时候除以 \(2^n\) 即可。

从值域上考虑,发现根本没法描述位置的事情,所以考虑直接从位置上考虑。

考虑如果给定一个序列,最后剩下的下标如何确定。我们从后往前扫,假设当前高度为 \(h_i\),如果 \([x,h_i]\) 中的数都已经出现过,那么这个位置的高度就是 \(x-1\),当然 \(0\) 就不保留。称这些保留的位置为关键点。

那么我们可以根据这个东西去设计一个 dp,\(dp_{i,j}\) 代表后 \(i\) 个数,目前关键点构成的元素已经填满了 \([1,j]\)\(j+1\) 一定没有,剩余的有没有都可以。记当前后缀中出现了 \(cnt0\) 次非关键点,\(cnt1\) 次关键点。

考虑对于关键点和非关键点转移。

  • 非关键点。

那么要求一定是在 \([1,j]\) 内的,否则就会留下来。因为已经用掉了 \(cnt_0\) 次,同时 \([1,j]\) 各用了一次,所以系数为 \(j-cnt_0\)

\[dp_{i-1,j}\leftarrow dp_{i,j}(j-cnt_0) \]

  • 关键点。

第一种是我加了,但是没加到 \(j+1\) 这个位置,那这样就不会有新的连续段,貌似贡献不好统计。不好统计那么我们就留到后面在确定,所以转移式:

\[dp_{i-1,j}\leftarrow dp_{i,j} \]

第二种是我加到 \(j+1\) 这个位置,枚举一下转移到 \(k\),那么我需要在前面没确定的那些位置里随便选几个出来组成 \([j+2,k]\),方案数为 \(\binom{cnt1-j}{k-j-1}\)。然后考虑我这个数的选法,为 \([j+2,k]\) 加起来有 \(k-j-1\) 种,还有 \(2\) 种是选 \(j+1\),方案数 \(k-j+1\),最后还要乘上我形成长度为 \(k-j-1\) 的段的方案数,这里用 \(g_{k-j-1}\) 表示,等会儿我们会具体来解释 \(g\) 的求法。有转移式:

\[dp_{i-1,k}\leftarrow dp_{i,j}\binom{cnt1-j}{k-j-1}(k-j+1)g_{k-j-1} \]

最后考虑 \(g\) 怎么求,直接枚举上一个位置选的是哪里,然后类似上面这个计算方式,有转移式:

\[g_n=\sum_{i=1}^n \binom{n-1}{i-1}g_{i-1}g_{n-i}(n-i+2) \]

代码:

#include <bits/stdc++.h>
using namespace std;
#define int long long
const int maxn = 1205, mod = 1e9 + 7;
int n, use[maxn], g[maxn], C[maxn][maxn], dp[maxn][maxn];
signed main() {
	cin >> n;
	for (int x, i = 1; i <= n; i++)
		cin >> x, use[x] = 1;
	n <<= 1;
	C[0][0] = 1;
	for (int i = 1; i <= n; i++) {
		C[i][0] = 1;
		for (int j = 1; j <= i; j++)	
			C[i][j] = (C[i - 1][j - 1] + C[i - 1][j]) % mod;
	}
	g[0] = 1;
	for (int i = 1; i <= n; i++) {
		for (int j = 1; j <= i; j++)
			g[i] = (g[i] + g[j - 1] * C[i - 1][j - 1] % mod * g[i - j] % mod * (j + 1) % mod) % mod;
	}
	dp[n + 1][0] = 1;
	int cnt[2] = {0, 0};
	for (int i = n; i >= 1; i--) {
		if(use[i]) {
			cnt[1]++;
			for (int j = cnt[0]; j <= cnt[1] - 1; j++) {
				dp[i][j] = (dp[i][j] + dp[i + 1][j]) % mod;
				for (int k = j + 1; k <= cnt[1]; k++) 
					dp[i][k] = (dp[i][k] + dp[i + 1][j] * (k - j + 1) % mod * C[cnt[1] - j - 1][k - j - 1] % mod * g[k - j - 1] % mod) % mod;
			}
		}
		else {
			cnt[0]++;
			for (int j = cnt[0]; j <= cnt[1]; j++)
				dp[i][j] = (dp[i][j] + dp[i + 1][j] * (j - cnt[0] + 1)) % mod;
		}
	//	for (int j = cnt[0]; j <= cnt[1]; j++)
	//		cout << i << " " << j << " " << dp[i][j] << endl;
	}
	for (int i = 1; i <= n / 2; i++)
		dp[1][n / 2] = dp[1][n / 2] * (mod + 1) / 2 % mod;
	cout << dp[1][n / 2] << endl;
	return 0;
}
posted @ 2025-10-31 09:07  LUlululu1616  阅读(13)  评论(0)    收藏  举报