【题解】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\) 条边的方案。显然有
链上的问题就非常清晰了,模拟一下可以得出
最后我们将所有环的答案合并,本质就是一个背包。
对于 \(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;
}