[组合计数] P5339 [TJOI2019] 唱、跳、rap和篮球

posted on 2024-04-23 13:11:26 | under | source

被 CXK 殴打了(悲

显然容斥,现在要求钦定 \(i\) 组 CXK 后、其它点乱排的方案数。

对于前者显然有个傻乎乎的平方 \(\rm dp\) 做法。不过考虑将 CXK 缩成一个点,那么总共 \(n-3i\) 个点,在里面选择 \(i\) 个展开成 CXK 即可。答案就是 \(C(n-3i,i)\)

然后其它点乱排,是不是也能容斥?很遗憾,容斥后对枚举的每个子集的方案数依旧很难算,稍有不慎就会算重。所以思考容斥外的做法。

可不可以 \(\rm dp\)?不太可行,因为这是可重集计数,还是要记录下每种点的数量,复杂度不够好。

直到看到 yyc 学长题解提到的 \(\rm EGF\) 后,才恍然大悟——这不就是 \(\rm EGF\) 板子吗?直接单次 \(n^2\) 暴力碾过去就好了。

代码

#include<bits/stdc++.h>
using namespace std;

#define int long long
const int N = 1e3 + 5, mod = 998244353; 
int n, a[4], b[4], jc[N << 2], jcinv[N << 2], ans, c[N];

inline int qstp(int a, int k) {int res = 1; for(; k; a = a * a % mod, k >>= 1) if(k & 1) res = res * a % mod; return res;}
inline void init() {jc[0] = jcinv[0] = 1; for(int i = 1; i < N; ++i) jcinv[i] = qstp(jc[i] = jc[i - 1] * i % mod, mod - 2);}
inline int C(int n, int m) {if(n < 0 || m < 0 || n < m) return 0; return jc[n] * jcinv[n - m] % mod * jcinv[m] % mod;}
inline int calc(int n, int b[]){
	if(b[0] < 0 || b[1] < 0 || b[2] < 0 || b[3] < 0) return 0;
	memset(c, 0, sizeof c), c[0] = 1;
	for(int t = 0, s = b[0]; t < 4; s += b[++t])
		for(int i = min(n, s); ~i; --i){
			int res = 0;
			for(int j = max(0ll, i - b[t]); j <= i; ++j) res = (res + c[j] * jcinv[i - j]) % mod; 
			c[i] = res;
		}
	return jc[n] * c[n] % mod;
}
signed main(){
	init();
	scanf("%lld%lld%lld%lld%lld", &n, &a[0], &a[1], &a[2], &a[3]);
	for(int i = 0; i <= n / 4; ++i){
		b[0] = a[0] - i, b[1] = a[1] - i, b[2] = a[2] - i, b[3] = a[3] - i;
		int res = C(n - 3 * i, i) * calc(n - 4 * i, b) % mod;
		ans = (ans + (i & 1 ? -1 : 1) * res + mod) % mod;
	}
	cout << ans;
	return 0;
}
posted @ 2026-01-13 11:26  Zwi  阅读(0)  评论(0)    收藏  举报