【学军NOIP开放题2-C】底夫斯拉夫(容斥)(DP)

底夫斯拉夫

题目链接:学军NOIP开放题2-C

题目大意

给你一个数组,问你有多少个排列方式中每一个数都是混乱的。
定义一个数组中一个数是混乱的当且仅当它左边有比它到大的数或者右边有比它小的数。

思路

考虑用容斥,所有的方案减去至少有一个数不是混乱的方案。

然后我们把数组从小到大排序,依次加入,设 \(f_{i}\) 为搞定长度为 \(i\) 的方案数。

然后我们先处理没有重复数字的,再拿总的 \(n!\) 减去。
那就是我们考虑枚举第一不是混乱的地方 \(k\),那 \(k-1\) 的部分就是 \(f_{k-1}\),不能混乱,然后剩下的可以随便摆,就是 \((i-k)!\)

然后考虑有重复的,那就相当于一个错排,\(g_{l,r}\) 为搞定 \(l\sim r\) 的部分的排法种数。
然后 \(i!\) 就变成了 \(g_{1,i}\)\((i-k)!\) 就变成了 \(g_{k+1,i}\)

然后就是 \(g_{i,j}=\dfrac{(j-i+1)!}{\prod a_k!}\)

然后预处理 \(g\) 即可。

代码

#include<cstdio>
#include<algorithm>
#define ll long long
#define mo 998244353

using namespace std;

int n, a[8005];
ll jc[8005], ans, f[8005], g[8005][8005], inv[8005];

int main() {
	freopen("dfslover.in", "r", stdin);
	freopen("dfslover.out", "w", stdout);
	
	scanf("%d", &n);
	for (int i = 1; i <= n; i++)
		scanf("%d", &a[i]);
	sort(a + 1, a + n + 1);
	
	jc[0] = 1;
	for (int i = 1; i <= n; i++) jc[i] = jc[i - 1] * i % mo;
	inv[0] = inv[1] = 1;
	for (int i = 2; i <= n; i++)
		inv[i] = (mo - mo / i) * inv[mo % i] % mo;
	
	for (int i = 1; i <= n; i++) {
		g[i + 1][i] = g[i][i] = 1;
		int sam = 1;
		for (int j = i + 1; j <= n; j++) {
			if (a[j] == a[j - 1]) sam++;
				else sam = 1;
			g[i][j] = g[i][j - 1] * (j - i + 1) % mo * inv[sam] % mo;
		}
	}
	
	f[0] = 1;
	for (int i = 1; i <= n; i++) {
		f[i] = g[1][i];
		for (int j = 1; j <= i; j++)
			f[i] = (f[i] - f[j - 1] * g[j + 1][i] % mo + mo) % mo;
	}
	
	printf("%lld", f[n]);
	
	return 0;
}
posted @ 2021-11-17 21:27  あおいSakura  阅读(32)  评论(0)    收藏  举报