[CF715E] Complete the Permutations(dp+组合计数)
Problem
给定两个长度为 \(n\) 的排列 \(a,b\),但是其中有些位置未知,用 \(0\) 表示。
定义两个排列的距离为:每次选择 \(a\) 中的两个元素交换,使其变为 \(b\) 的最小次数。
要求补全两个排列,求补全之后 \(a,b\) 距离为 \(i\) \((i∈[0,n-1])\) 的方案数。
\(n ≤ 250\),答案对 \(998244353\) 取模。
Solution
先考虑怎么算补全之后 \(a,b\) 的距离:
对于每个 \(i\) \((i∈[1,n])\),连边 \(a_i→b_i\)。
记连边后的图上环的个数为 \(m\),那么 \(a,b\) 的距离为 \(n-m\)。
回到原问题:
我们建 \(n\) 个点 \(p_1,p_2,...,p_n\) 表示 \(n\) 个位置,建 \(n\) 个点 \(v_1,v_2,...,v_n\) 表示 \(1\) ~ \(n\) 这 \(n\) 个数值。
对于每个 \(i\) \((i∈[1,n])\),如果 \(a_i≠0\),连边 \(v_{a_i}→p_i\)。如果 \(b_i≠0\),连边 \(p_i→v_{b_i}\)。得到一张初始的图。
在这张图中,\(v_x→p_y\) 表示补全后的 \(a_y=x\),\(p_x→v_y\) 表示补全后的 \(b_x=y\)。
只能在 \(v_i\) 和 \(p_j\) 之间连边,不许 \(v_i\) 和 \(v_j\) 连边,也不许 \(p_i\) 和 \(p_j\) 连边。
初始的图中有一些环和链(包括单点成链)。
我们要做的就是加一些边(这些边只能从一条链的结尾连向一条链的开头,可以是同一条链的结尾和开头),使得最终的图有 \(x\) \((x∈[1,n])\) 个环,\(0\) 条链。
记初始的图中:
环有 \(c_0\) 个。
以 \(p\) 开头,\(v\) 结尾的链(\(1\) 类链)有 \(c_1\) 条。
以 \(v\) 开头,\(p\) 结尾的链(\(2\) 类链)有 \(c_2\) 条。
以 \(p\) 开头,\(p\) 结尾的链(\(3\) 类链)有 \(c_3\) 条。
以 \(v\) 开头,\(v\) 结尾的链(\(4\) 类链)有 \(c_4\) 条。
因为环上必须是 \(v,p\) 交替出现的,所以一个环的组成可以是:
- 仅 \(1\) 类链组成。
- 仅 \(2\) 类链组成。
- 由 \(1,2,3,4\) 类链组成。
- 由 \(3,4\) 类链组成。
- 由 \(1,3,4\) 类链组成。
- 由 \(2,3,4\) 类链组成。
我们先考虑 \(1,2,3\) 类链之间怎么连边。
记 \(f_i\) 表示满足 \(\lceil\) 有 \(i\) 个仅 \(1\) 类链组成的环 \(\rfloor\) 的情况下,\(1\) 类链的连边方案数。
因为 \(1\) 类链是 \(p\) 开头 \(v\) 结尾,所以我们考虑的 \(\lceil\) 连边方案数 \(\rfloor\) 也就是给每个 \(1\) 类链的结尾连一条出边的方案数。
显然这个出边要么连向 \(1\) 类链的开头,要么连向 \(3\) 类链的开头。
\(\lceil\) 恰好有 \(i\) 个仅 \(1\) 类链组成的环 \(\rfloor\) 的方案数不好算,考虑让 \(f_i\) 先表示 \(\lceil\) 至少有 \(i\) 个仅 \(1\) 类链组成的环 \(\rfloor\) 的方案数。
枚举这 \(i\) 个 \(\lceil\) 仅 \(1\) 类链组成的环 \(\rfloor\) 用了 \(j\) 条 \(1\) 类链。
我们要从 \(c_1\) 条 \(1\) 类链中选出 \(j\) 条,把它们排成 \(i\) 个环。
剩下的 \(c_1-j\) 条 \(1\) 类链,要么连向 \(1\) 类链,要么连向 \(3\) 类链。显然是不可以连向用来成环的 \(j\) 条 \(1\) 类链的,那么就有 \(A_{c_1-j+c_3}^{c_1-j}\) 种方案。
于是可得递推式:
其中 \(C\) 是组合数,\(S\) 是第一类斯特林数,\(A\) 是排列数。
然后计算\(\lceil\) 恰好有 \(i\) 个仅 \(1\) 类链组成的环 \(\rfloor\) 的方案数:
这样我们就把 \(1\) 类链的出边(结尾连出去的边)都搞定了。
接下来搞定 \(2\) 类链的入边。
和 \(1\) 类链同理,记 \(g_i\) 表示满足 \(\lceil\) 有 \(i\) 个仅 \(2\) 类链组成的环 \(\rfloor\) 的情况下,\(2\) 类链的连边方案数。跟 \(f_i\) 计算方法一样。
截至目前,除掉所有的环以及 \(4\) 类链,有下面 \(4\) 种长链((\(x\))\(_n\) 表示连续若干个 \(x\)):
- (\(1\) 类链)\(_n→\) \(3\) 类链 \(→\) (\(2\) 类链)\(_n\)
- (\(1\) 类链)\(_n→\) \(3\) 类链
- \(3\) 类链 \(→\) (\(2\) 类链)\(_n\)
- \(3\) 类链
发现这 \(4\) 种长链有两个共有的特点:
- 只含 \(1\) 条 \(3\) 类链。
- 开头和结尾一定都是 \(p\)。
把这 \(4\) 种长链和所有的 \(4\) 类链一起串成环,我们就完成任务了。
我们令 \(h=f×g\)。
记 \(ans_i\) 表示最终的图有 \(i\) 个环的方案数。
枚举仅由 \(1\) 类链组成的环、仅由 \(2\) 类链组成的环共 \(j\) 个,那么我们要把上述 \(4\) 种长链和所有的 \(4\) 类链串成 \(i-j\) 个环。
因为每条长链里必定只含 \(1\) 条 \(3\) 类链,所以我们可以给每条长链分别编号 \(1\) ~ \(c_3\)。
显然最终形成的环一定是长链和 \(4\) 类链交替出现。
那么:
表示将每条长链先分别接 \(1\) 条 \(4\) 类链,然后摆成 \(i-j\) 个环。
摆环的方案数显然是 \(S_{c_3}^{i-j}\)。
因为 \(c_3=c_4\),所以长链和 \(4\) 类链的连接方案数为 \(c_4!\)。
时间复杂度 \(o(n^2)\)。
#include <bits/stdc++.h>
using namespace std;
#define ll long long
template <class t>
inline void read(t & res)
{
char ch;
while (ch = getchar(), !isdigit(ch));
res = ch ^ 48;
while (ch = getchar(), isdigit(ch))
res = res * 10 + (ch ^ 48);
}
const int e = 2005, mod = 998244353;
int S[e][e], nxt[e], C[e][e], A[e][e], a[e], b[e], n, ans[e], f[e], g[e], h[e], fac[e];
int c0, c1, c2, c3, c4, deg[e], ret[e];
bool vis[e];
inline int plu(int x, int y)
{
(x += y) >= mod && (x -= mod);
return x;
}
inline int sub(int x, int y)
{
(x -= y) < 0 && (x += mod);
return x;
}
inline void dfs(int x, bool s, bool t)
{
vis[x] = 1;
int y = nxt[x];
if (y)
{
if (vis[y]) c0++;
else dfs(y, s, t ^ 1);
}
else
{
if (!s && t) c1++;
else if (s && !t) c2++;
else if (s && t) c3++;
else c4++;
}
}
inline void init(int cnt, int *f)
{
int i, j;
for (i = 0; i <= cnt; i++)
for (j = 0; j <= cnt; j++)
f[i] = (f[i] + (ll)C[cnt][j] * S[j][i] % mod * A[cnt - j + c3][cnt - j]) % mod;
for (i = cnt; i >= 0; i--)
for (j = i + 1; j <= cnt; j++)
f[i] = sub(f[i], (ll)f[j] * C[j][i] % mod);
}
int main()
{
int i, j;
read(n);
for (i = 1; i <= n; i++) read(a[i]);
for (i = 1; i <= n; i++) read(b[i]);
for (i = 1; i <= n; i++)
{
if (a[i]) nxt[a[i] + n] = i, deg[i]++;
if (b[i]) nxt[i] = b[i] + n, deg[b[i] + n]++;
}
for (i = 1; i <= 2 * n; i++)
if (!vis[i] && !deg[i]) dfs(i, i > n, i > n);
for (i = 1; i <= 2 * n; i++)
if (!vis[i]) dfs(i, i > n, i > n);
C[0][0] = A[0][0] = S[0][0] = fac[0] = 1;
for (i = 1; i <= n; i++)
{
C[i][0] = A[i][0] = 1;
fac[i] = (ll)fac[i - 1] * i % mod;
for (j = 1; j <= i; j++)
{
C[i][j] = plu(C[i - 1][j - 1], C[i - 1][j]);
A[i][j] = (A[i - 1][j] + (ll)A[i - 1][j - 1] * j) % mod;
S[i][j] = (S[i - 1][j - 1] + (ll)S[i - 1][j] * (i - 1)) % mod;
}
}
init(c1, f); init(c2, g);
for (i = 0; i <= n; i++)
for (j = 0; j <= i; j++)
h[i] = (h[i] + (ll)f[j] * g[i - j]) % mod;
for (i = 0; i <= n; i++)
for (j = 0; j <= i; j++)
ans[i] = (ans[i] + (ll)h[j] * S[c3][i - j] % mod * fac[c4]) % mod;
for (i = 0; i < n; i++)
if (n - i - c0 >= 0) ret[i] = ans[n - i - c0];
else ret[i] = 0;
for (i = 0; i < n - 1; i++) printf("%d ", ret[i]);
printf("%d\n", ret[n - 1]);
return 0;
}