Loading

【题解】ABC214G - Three Permutations

很神的数数题,技巧性和思维难度都很高。

我们要求不存在一个位置相同,直接容斥转化为对于每个 \(i\in[0,n]\) 求存在 \(i\) 个位置相同。

题目的限制条件是给定恰好两个排列 \(P,Q\),而不是给定多个排列,考虑其中的意义。

对于排列,等价于一个由若干个环组成的有向图。思考一下不难发现如果我们将 \(i\to P_i\) 的边改为 \(P_i\to Q_i\) 的边,这仍然是一个由若干个环组成的有向图。

为什么要这样转换,因为转换之后,一个位置变成了一条边,那么一个位置相等,等价于选择一条边的一个节点。

所以我们可以先选出若干条边,然后对每条边固定一个节点,两条边固定的节点不能相同。

根据图的性质,选出的边构成的子图,由简单链和简单环和自环组成。

对于自环,只能固定自己。对于简单环,只有固定每条边的起点和固定每条边的中点两种方案。

对于一条链,显然存在一个断点,对于断点前面的点固定起点,后面的点固定终点,所以由链的节点数种方案。

原图中不同环之间互不干扰,所以对每个环分开求。

由于环的相似性,我们可以直接定义 \(g[i][j]\) 表示 \(i\) 个点的环,选了 \(j\) 条边,有多少种选边并固定点的方案。

由于环是对称的,直接求非常麻烦,我们枚举环上第一节点所在链的长度 \(k\) ,然后转化为求 \(f[i][j]\) 表示长度为 \(i\) 的链,选了 \(j\) 条边的方案。显然有

\[g[i][j] = \sum\limits_{k = 0}^j(k+1)^2f[i-k-1][j-k] \]

链上的问题就非常清晰了,模拟一下可以得出

\[f[i][j] = \sum\limits_{k = i - j - 1}^{i- 1}(i-k)f[k][j - (i - k - 1)] \]

最后我们将所有环的答案合并,本质就是一个背包。

对于 \(f\) ,前缀和优化做到 \(\mathcal{O}(N^2)\),对于 \(g\),我们只用求特定的 \(i\),且 \(\sum i = n\),所以总的时间复杂度也是 \(\mathcal{O}(N^2)\)

官方题解的组合数做法好神啊,没看懂(

#define N 3005

int f[N][N], g[N], n, p[N], q[N], u[N], c[N], v[N], fac[N], d[N], s[N][N], t[N][N];
int calc(int x){
	v[x] = 1;
	if(!v[u[x]])return calc(u[x]) + 1;
	return 1;
}
int main() {
	//int T = read();while(T--)solve();
	n = read(), fac[0] = 1;
	rp(i, n)fac[i] = 1LL * fac[i - 1] * i % P;
	rp(i, n)p[i] = read();
	rp(i, n)q[i] = read(), u[p[i]] = q[i];
	
	//calc :: f
	f[0][0] = s[0][0] = 1;
	rp(i, n){
		rep(j, 0, i - 1){
			ad(f[i][j], 1LL * i * s[i - 1][i - j - 1] % P);
			su(f[i][j], t[i - 1][i - j - 1]);
			s[i][i - j] = (s[i - 1][i - j] + f[i][j]) % P;
			t[i][i - j] = (t[i - 1][i - j] + 1LL * i * f[i][j] % P) % P;
		}
		s[i][0] = 1;
	}
	c[0] = 1; int sz = 0;
	rp(i, n)if(!v[i]){
		memset(d, 0, sizeof(d));
		memset(g, 0, sizeof(g));
		int ss = calc(i);
		
		//calc :: g
		g[ss] = 2, g[0] = 1;
		rep(j, 1, ss - 1)
			rep(k, 0, j)
				ad(g[j], 1LL * (k + 1) * (k + 1) * f[ss - k - 1][j - k] % P);
		if(1 == ss)g[ss] = 1;
		
		//calc :: c
		rep(j, 0, sz)rep(k, 0, ss)
			ad(d[j + k], 1LL * c[j] * g[k] % P);
		sz += ss;
		rep(j, 0, sz)c[j] = d[j];
		
	}
	int ans = 0, op = 1;
	
	rep(i, 0, n){
		ad(ans, 1LL * fac[n - i] * (P + op * c[i]) % P), op = -op;
	}
	cout << ans << endl;
	return 0;
}
posted @ 2021-10-05 17:33  7KByte  阅读(200)  评论(0编辑  收藏  举报