前置:多重集排列组合问题
设:多重集(S = left{ n_1 cdot a_1 , n_2 cdot a_2 ,..., n_k cdot a_k
ight}.) 即由(n_1) 个 (a_1),(n_2) 个 (a_2)......组成的集合,(n = n_1 + n_2 +...+n_k)。
其全排列的个数为:
设整数(r le n_i (forall i in [1,k])),从 S 中取 r 个元素的方案数为:
证明:
设取了 (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,可得:
一个环拆成两个环之后,接下来对这两个环的操作就互相独立了,并且 x 环 x - 1 次,y 环 y - 1 次,这 x + y - 2 次操作也可以看成一个多重集全排列,所以:
再看最初的 m 个环,第(s_i) 个环操作 (s_i - 1) 次,每次操作可以从 m 个环中任选一个,这样又构成一个多重集全排列:
这样时间复杂度为(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
", ans);
}
return 0;
}