P4778 组合计数
作者:互联网
题意
题解
考虑对排序的每一个位置 i i i 向位置 P i P_i Pi 连一条边,可以得到点数、边数都为 n n n 的图。显然有序的排列由 n n n 个自环构成,考虑不断将大的环拆开。
把一个长度为 n n n 的环变成 n n n 个自环,最少需要 n − 1 n-1 n−1 次交换操作。
使用数学归纳法可证明上述引理,考虑将一个较大规模的环拆成两个较小规模的环即可。
设初始排列有
r
r
r 个环,那么最少的操作次数为
n
−
r
n-r
n−r。计算方案数时,按照拆环的思路求解。设将长度为
n
n
n 的环拆为
n
n
n 个自环的方案数为
F
(
n
)
F(n)
F(n)。根据 加法原理,对将长度为
n
n
n 的环拆为长度为不同
x
x
x 和
y
y
y 的环的方案数求和;根据 乘法原理,将长度为
n
n
n 的环拆为长度为
x
x
x 和
y
y
y 的环,拆开位置左侧的端点有
f
(
x
,
y
)
f(x,y)
f(x,y) 种可能,长度为
x
,
y
x,y
x,y 的环分别拆为
x
,
y
x,y
x,y 个自环有
F
(
x
)
,
F
(
y
)
F(x),F(y)
F(x),F(y) 种可能,要对上述拆分步骤进行乘积;根据 多重集的排列数,当确定拆开长度为
n
n
n 的环的第一步交换操作后,需要统计后续
n
−
2
n-2
n−2 步的排列数,考虑到缩小规模的环
F
(
x
)
,
F
(
y
)
F(x),F(y)
F(x),F(y) 已经计算了排列数,那么将其各步骤看作重复元素进行多重集的排列数计算。
f
(
x
,
y
)
=
{
n
/
2
x
=
y
n
x
≠
y
f(x,y)=\begin{cases} n/2 & x=y\\ n & x\neq y\\ \end{cases}
f(x,y)={n/2nx=yx=y
F
(
n
)
=
∑
x
+
y
=
n
,
x
≤
y
f
(
x
,
y
)
×
F
(
x
)
×
F
(
y
)
×
(
n
−
2
)
!
(
x
−
1
)
!
×
(
y
−
1
)
!
F(n)=\sum\limits_{x+y=n,x\leq y}f(x,y)\times F(x)\times F(y)\times \frac{(n-2)!}{(x-1)!\times (y-1)!}
F(n)=x+y=n,x≤y∑f(x,y)×F(x)×F(y)×(x−1)!×(y−1)!(n−2)! 预处理
F
(
n
)
F(n)
F(n) 的复杂度是
O
(
n
2
)
O(n^2)
O(n2),显然难以胜任,考虑打表观察规律,可以得到
F
(
n
)
=
n
n
−
2
F(n)=n^{n-2}
F(n)=nn−2 快速幂求解,时间复杂度为
O
(
n
log
n
)
O(n\log n)
O(nlogn)。
#include <bits/stdc++.h>
using namespace std;
#define maxn 100005
typedef long long ll;
const int mod = 1000000009;
int T, N, P[maxn];
bool used[maxn];
ll mem[maxn], f[maxn], inv[maxn], fv[maxn];
int dfs(int i)
{
if (used[i])
return 0;
used[i] = 1;
return dfs(P[i]) + 1;
}
ll F(int l)
{
if (mem[l])
return mem[l];
ll n = l - 2, x = l, res = 1;
while (n)
{
if (n & 1)
res = res * x % mod;
x = x * x % mod;
n >>= 1;
}
return mem[l] = res;
}
int main()
{
f[0] = f[1] = inv[1] = fv[0] = fv[1] = 1;
for (int i = 2; i < maxn; ++i)
{
f[i] = i * f[i - 1] % mod;
inv[i] = (mod - mod / i) * inv[mod % i] % mod;
fv[i] = inv[i] * fv[i - 1] % mod;
}
mem[1] = 1;
scanf("%d", &T);
while (T--)
{
scanf("%d", &N);
for (int i = 1; i <= N; ++i)
scanf("%d", P + i);
memset(used, 0, sizeof(bool) * (N + 1));
ll res = 1;
int r = 0;
for (int i = 1; i <= N; ++i)
{
if (!used[i])
{
int l = dfs(i);
++r;
res = res * F(l) % mod * fv[l - 1] % mod;
}
}
res = res * f[N - r] % mod;
printf("%lld\n", res);
}
return 0;
}
标签:return,fv,组合,int,res,P4778,计数,maxn,mod 来源: https://blog.csdn.net/neweryyy/article/details/110456674