Counting swaps
题面
Counting swaps
洛谷题面有误,n 的数据范围应该是 1e5。
题解
模数看错了,调了好久。。。
题目的要求是把一个 1 ~ n 的排列,变成单增数列,使交换次数最小,求方案数。
最后的排列一定是 1,2,3,…,n。
所以每个数都它们对应的数值的位置。
所以对于初始序列把每个数向它的的数值的位置连一条边,这样就会产生许多环。
对于每一条边,就代表 u 想换到 v 去,所以我们为了交换次数最小,完全可以在环内处理,就沿着每条边进行交换,可这样交换后的次数是最小的吗?
这是我们就需要引理了。
根据小蓝书里面的引理:
- 把一个长度为 n 的环变成 n 个自环,最小需要 n - 1 次交换操作。
- 有 \(n^{n - 2}\) 种交换方案。
所以我们的策略是正确的,所以我们只用依次找出每个环的大小就能得出每个环的方案数.
然后就只剩下了求总的方案数,每个环之间是不相互影响的,那么这个问题就是一个多重集的排列问题,根据多重集的排列公式我们可以得到最后的答案为:
设大小为 n 的环的方案数是 \(F_n\)。
\[F_{l_1} * F_{l_2} * … * F_{l_k} * (n - k)! / (l_1 - 1)! * (l_2 - 1)! * … * (l_k - 1)!
\]
代码
#include<cstdio>
#include<cstring>
const int N = 1e5 + 5,mod = 1e9 + 9;
int n,fac[N],f[N],ans,inv,cnt,t;
int to[N]; bool dfn[N];
int power(int a,int b,int res = 1) {
for(; b; b >>= 1,a = 1ll * a * a % mod)
if(b & 1) res = 1ll * res * a % mod;
return res;
}
int main() {
fac[0] = 1;
for(int i = 1; i <= N; i++) fac[i] = 1ll * i * fac[i - 1] % mod;
f[1] = 1;
for(int i = 2; i < N; i++) f[i] = power(i,i - 2);
scanf("%d",&t);
while(~scanf("%d",&n)) {
ans = inv = 1; cnt = 0;
memset(dfn,false,sizeof(dfn));
for(int i = 1; i <= n; i++) scanf("%d",&to[i]);
for(int i = 1,siz = 1; i <= n; i++,siz = 1)
if(!dfn[i]) {
dfn[i] = true; ++cnt;
for(int x = i; x; x = to[x],siz++,dfn[x] = true)
if(dfn[to[x]]) break;
if(siz > 2) ans = 1ll * ans * f[siz] % mod;
inv = 1ll * inv * power(fac[siz - 1],mod - 2) % mod;
}
ans = 1ll * ans * fac[n - cnt] % mod;
ans = 1ll * ans * inv % mod;
printf("%d\n",ans); if(!(--t)) return 0;
}
}
/*
3
3
2 3 1
4
2 1 4 3
2
1 2
*/

浙公网安备 33010602011771号