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
*/
posted @ 2021-06-03 21:18  init-神眷の樱花  阅读(54)  评论(0)    收藏  举报