CH3602 Counting Swaps(组合计数)

前置:多重集排列组合问题
设:多重集\(S = \left\{ n_1 \cdot a_1 , n_2 \cdot a_2 ,..., n_k \cdot a_k \right\}.\) 即由\(n_1\)\(a_1\)\(n_2\)\(a_2\)......组成的集合,\(n = n_1 + n_2 +...+n_k\)
其全排列的个数为:

\[\begin{aligned} \frac{n!}{n_1!\cdot n_2! \cdot ... \cdot n_k!} \end{aligned} \]

设整数\(r \le n_i (\forall i \in [1,k])\),从 S 中取 r 个元素的方案数为:

\[\begin{aligned} C^{k - 1}_{r + k - 1} \end{aligned} \]

证明:
设取了 \(x_i\)\(a_i\)\(i \in [1, k]\) ,因为 \(r \le n_i\),所以 \(x_i \le n_i\) ,在 r 个 1的字符串中插入k - 1个0,将其分成 k 段,第 i 段的 1 数量表示 \(x_i\) ,所以,此问题转化为 k - 1 个 0 和 r 个 1 的全排列问题,所以:\(\frac{(r + k - 1)!}{r! \cdot (k - 1)!} = C^{k - 1}_{r + k - 1}\)

题目:3602 Counting Swaps
题意:给定一个 1~n 的排列 \(p_1,p_2,…,p_n\),进行若干次操作,每次选择两个整数 x,y,交换 \(p_x,p_y\)。设把 \(p_1,p_2,…,p_n\) 变成排列 \(1,2,…,n\) 至少需要 m 次交换。求有多少种操作方法可以只用 m 次交换。输出对 10^9+9 取模之后的值。1≤n≤10^5。

将n个数看成n个点,将第 i 个点向第 \(p_i\) 个点连一条边,当排列到达最终状态时,整个图为 n 个自环,所以我们只需要找出图中的所有环,然后将其拆为自环。
若 x ,y 位于两个不同的环内,交换这两个数显然会使总环数 - 1,所以这种操作没有意义,我们只需要在同一个环内操作。
性质: 将一个 n 个点的环拆为 n 个自环最少需要 n - 1 步操作。 可用数学归纳法证明。
设: 整张图有 m 个环,每个环的结点数分别为 \(s_1, s_2 , ... , s_m\)
设: \(f_n\) 为将一个 n 个点的环拆为自环的方案数,\(T_{(x, y)}\) 为将一个 n 个点的环拆为两个点数为 x,y 的环这一次操作的方案数,x + y = n,可得:

\[ T_{(x, y)} = \left\{ \begin{aligned} x + y \qquad \qquad x \ne y \\ x \quad \qquad \qquad x = y \end{aligned} \right. \]

一个环拆成两个环之后,接下来对这两个环的操作就互相独立了,并且 x 环 x - 1 次,y 环 y - 1 次,这 x + y - 2 次操作也可以看成一个多重集全排列,所以:

\[\begin{aligned} f_n = \sum_{x + y = n}^{x \leq \frac{n}{2} } f_x \cdot f_y \cdot T_{(x, y)} \cdot \frac{(x + y - 2)! }{(x - 1)! \cdot (y - 1)! } \end{aligned} \]

再看最初的 m 个环,第\(s_i\) 个环操作 \(s_i - 1\) 次,每次操作可以从 m 个环中任选一个,这样又构成一个多重集全排列:

\[Ans = (\prod_{i = 1}^m {f_{s_i}} ) \cdot \frac{(n - m)! }{ (s_1 - 1)! \cdot (s_2 - 1)! \cdot ... \cdot (s_m - 1)! } \]

这样时间复杂度为\(O(n^2logn)\),超时。
打表输出 \(f_n\) 的前几项:

1 1 3 16 125 1296 16807 262144 4782969 100000000 2357947691

可以发现规律\(f_n = n^{ n - 2}\), 这样可以将复杂度降到\(O(nlogn)\)

#include <iostream>
#include <cstdio>
#include <cstring>
#include <cmath>
#include <algorithm>
using namespace std;
typedef long long lld;
const lld p = 1e9 + 9;
const int N = 100005;
int nextt[N], to[N], head[N], cnt = 0;
int n, tot = 0;
lld a[N], s[N], f[N];
bool vis[N];
void add(int x, int y) {
	head[x] = nextt[++cnt];
	to[cnt] = y; head[x] = cnt;
}
lld powe(lld a, lld b) {
	if(b < 0) return 1;
	lld base = 1; 
	while(b) {
		if(b & 1) base = base * a % p;
		a = a * a % p; b >>= 1;
	}
	return base;
}
void dfs(int x) {
	s[tot]++; vis[x] = true;
	for(int i = head[x]; i; i = nextt[i]) {
		int y = to[i]; if(vis[y]) continue;
		dfs(y);
	}
}
int main() {
//	freopen("data.in", "r", stdin);
	int T; scanf("%d", &T); 
	f[0] = 1;
	for(lld i = 1; i < N; i++) {
		f[i] = f[i - 1] * i % p;
	}
	while(T--) {
		memset(s, 0, sizeof(s));
		memset(vis, false, sizeof(vis));
		memset(nextt, 0, sizeof(nextt));
		memset(head, 0, sizeof(head));
		tot = 0; cnt = 0;
		scanf("%d", &n);
		for(int i = 1; i <= n; i++) {
			scanf("%lld", &a[i]); add(i, a[i]);
		}
		for(int i = 1; i <= n; i++) {
			if(vis[i]) continue;
			vis[i] = true; ++tot; dfs(i);
		}
		lld ans = 1;
		ans = ans * f[n - tot] % p;
		for(int i = 1; i <= tot; i++) {
			ans = ans * powe(s[i], s[i] - 2) % p;
			ans = ans * powe(f[s[i] - 1], p - 2) % p;
		}
		printf("%lld\n", ans);
	}
	return 0;
}

posted @ 2020-05-06 15:40  Mcggvc  阅读(166)  评论(0编辑  收藏  举报