洛谷 P4778 Counting swaps 题解
Solution
根据排列 \(p_1,p_2,\dots,p_n\) 构造图 \(G\),\(G\) 上存在 \(n\) 个顶点,分别标为 \(1,2,\dots,n\),对于每个 \(i\) 建立一条 \(p_i\) 到 \(i\) 的边。
注意到,\(G\) 的每个连通分支都是一个环图,设初始状态下有 \(k\) 个环图。
交换序列中两个元素,会使图发生变化。接下来,为表述方便,我们不区分元素与其对应的顶点。
交换一个连通分支内的元素,会使该连通分支分裂为两个小环图;交换两个连通分支间的元素,会使两个连通分支合并为一个大环图。
当交换结束、排列变为自然排列时,\(G\) 由 \(n\) 个自环构成。
当采用最优交换策略时,每次交换必使 \(G\) 的连通分支数增加 \(1\),即交换某个连通分支内的元素。显然,\(m=n-k\)。
特别地,对于单个 \(L\) 阶环图,其最小交换次数为 \(L-1\)。
设初始状态下 \(k\) 个环图的阶分别为 \(L_1,L_2,\dots,L_k\),\(L_1+L_2+\dots+L_k=n\)。
设将一个 \(L\) 阶环图通过 \(L-1\) 步有序的交换变为 \(L\) 个自环的方案数为 \(f[L]\),如 \(f[3]=3\)。
初始状态下的 \(k\) 个环图交换策略互相独立,利用多重组合数,可求出总方案数为
接下来讨论如何计算 \(f\) 序列。
考虑所有顶点集为 \(V\) 的 \(L\) 阶环图,其中每个环图通过 \(L-1\) 步有序的交换变为 \(L\) 个自环的方案数均为 \(f[L]\),而 \(L\) 阶环图一共有 \((L-1)!\) 个,于是所有 \(L\) 阶环图的交换方案数总和等于 \((L-1)!\cdot f[L]\)。
我们根据一个环图构造一棵树:环图变为 \(L\) 个自环的每一步交换涉及一对元素,在一个新图中建立这些元素对的连边。由于交换共有 \(L-1\) 步,于是新图有 \(L-1\) 条边。因为这 \(L-1\) 步交换必将涉及 \(V\) 中所有元素,所以这个新图是连通的,进而这个新图是棵树。我们按照交换顺序给边建立编号:第一步交换构造的边标记为 \(1\),第二步交换构造的边标记为 \(2\)……
我们发现,每个环图的一种交换方案唯一对应着边带编号的树,而每个 \(V\) 上的边带编号的树也唯一对应着一种环图的交换方案(按照建树的过程逆向构造)。可以严谨证明,每个环图的一种交换方案与 \(V\) 上的边带编号的树间存在一一对应关系。于是,所有环图的交换方案数总和等于 \(V\) 上边带编号的树的数量。
由 Cayley 定理,不考虑边上编号时,树共有 \(L^{L-2}\) 棵。每棵树上再考虑边上编号的全排列,可知边带编号的树共有 \((L-1)!\cdot L^{L-2}\) 棵。由一一对应关系可知,所有 \(L\) 阶环图的交换方案数总和即 \((L-1)!\cdot L^{L-2}\)。
我们通过两种方法计算出了所有 \(L\) 阶环图的交换方案数总和,它们必然相等,于是有
我们找到了 \(f\) 序列的通项公式,至此问题全部解决。
Code
#include <bits/stdc++.h>
typedef long long i64;
using namespace std;
const i64 MOD = 1000000009ll;
i64 qpow(i64 x, i64 y) {
i64 res = 1;
while (y) {
if (y & 1)
res = res * x % MOD;
x = x * x % MOD;
y >>= 1;
}
return res;
}
i64 f(i64 l) {
if (l == 1)
return 1;
return qpow(l, l - 2);
}
int count(vector<int>& p, int x) {
int cnt = 0;
while (p[x]) {
++cnt;
int tmp = x;
x = p[x];
p[tmp] = 0;
}
return cnt;
}
i64 solve() {
int n;
cin >> n;
vector<int> p(n + 1);
for (int i = 1; i <= n; ++i)
cin >> p[i];
vector<int> L;
for (int i = 1; i <= n; ++i) {
int l = count(p, i);
if (l) L.emplace_back(l);
}
int k = L.size();
if (k == n) return 1;
i64 t = 1;
for (int i = 0; i < k; ++i)
t = t * f(L[i]) % MOD;
int M = n - k;
for (int i = 0; i < k; ++i)
M = max(--L[i], M);
vector<i64> fac(M + 1);
fac[0] = 1;
for (int i = 1; i <= M; ++i)
fac[i] = fac[i - 1] * i % MOD;
vector<i64> inv_fac(M + 1);
inv_fac[M] = qpow(fac[M], MOD - 2);
for (int i = M - 1; i >= 0; --i)
inv_fac[i] = inv_fac[i + 1] * (i + 1) % MOD;
for (int i = 0; i < k; ++i)
t = t * inv_fac[L[i]] % MOD;
return t * fac[n - k] % MOD;
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int t;
cin >> t;
while (t--)
cout << solve() << '\n';
return 0;
}

浙公网安备 33010602011771号