原题链接
题目分析
虽然要求的是方案数,但是最好还是先把至少需要几步弄好了再说。题目中每一个点都要到另一个点,不妨给这两个点连一条边。现在我给出一下样例:
6
:
2
1
3
5
6
4
6:2 1 3 5 6 4
6:2 1 3 5 6 4我们会得到如下图:
请注意,其中结点
3
3
3是自环。不难发现,每一个连通块都一定是一个简单环,因为每个点都只有一个出度、一个入度。我们的目标就是通过改变一些边使得原图变成
n
n
n个自环。
所以原问题就是分裂这些环,设环大小为
a
a
a,所以每个环至少需要
a
−
1
a-1
a−1步分裂成自环。
尝试在图中操作
现在我们来看一看某一个环中交换两个点会发生什么:
上图对应序列
2
3
4
5
1
2 3 4 5 1
2 3 4 5 1。接下来我们更换位置
2
2
2和位置
3
3
3(序列
2
4
3
5
1
2 4 3 5 1
2 4 3 5 1):
发现刚好把原图分成了两个环,让剩余的步数更少了。现在我们再看一看交换两个环上的点会发生什么:
原图对应序列
3
1
2
5
6
4
3 1 2 5 6 4
3 1 2 5 6 4现在我们交换位置
1
1
1和
4
4
4(序列
5
1
2
3
6
4
5 1 2 3 6 4
5 1 2 3 6 4):
发现我们把两个环变成了一个环,让需要交换的步数更多了!
性质的证明
我们刚刚画了图思考了一下,发现好像只有交换环内的点更优。那是不是一定如此呢?不难发现任意的序列都唯一对应一个只含简单环且没有环外边的图,任意的只含简单环且没有环外边的图都唯一对应一个序列。所以我们现在来分析图的性质,图的性质就是序列的性质。
如果交换了两个点
a
a
a和
b
b
b,其数值分别为
c
c
c和
d
d
d,那么就断掉了
a
→
b
a\to b
a→b和
c
→
d
c\to d
c→d的边,然后连接
a
→
d
a\to d
a→d和
b
→
c
b\to c
b→c。如果原来
a
a
a和
b
b
b在一个环内,就会断掉两条环上的边,然后就会形成了两条链,接着连接了两条边,不难发现这两条边连接的都是两条链的起点和终点,所以一定会分裂。如果
a
a
a和
b
b
b是两个环上的点,那就会分别断掉两个环,新连接的两条边就会连接成一个环。所以选择环内的任意两点交换都是可以的。
初步的dp
现在我们来考虑如何算出来这个方案数。首先我们先针对单个环处理。设 f i f_i fi表示大小为 i i i的环,其中 f 1 = 1 f_1=1 f1=1。一个环可以分成两个环,所以我们可以枚举这两个环的大小。然后选择两个点来分割,不难看出可以找到 n n n个分割的方案。但是请注意,如果分割的两部分大小一样就会出现重复,只有 n 2 \cfrac{n}{2} 2n个分割方案。设 T T T表示分割的方案数,则有状态转移方程: f i = ∑ x n 2 f x × f n − x × T f_i=\displaystyle\sum_x^{\frac{n}{2}}f_x\times f_{n-x}\times T fi=x∑2nfx×fn−x×T但是请注意,这个状态转移方程并不对!我们分割出来的两个集合并没有先后顺序,所以可以两个集合的操作可以混乱得放在一起,只需要保证两个集合内部是有序的。因为一个集合处理至少需要 x − 1 x-1 x−1步,所以 01 01 01串的长度为 n − 2 n-2 n−2。现在我们先假设吉和内部也可以随意摆放,所以方案数就是 ( n − 2 ) ! (n-2)! (n−2)!。然后单个集合内只能有序, 1 1 1种方案,但是第一个集合内随意摆放产生了 ( x − 1 ) ! (x-1)! (x−1)!个方案,除法计算即可。另一个集合同理。得到最终的状态转移方程: f i = ∑ x = 1 n 2 f x × f n − x × T × ( n − 2 ) ! ( x − 1 ) ! ( n − x − 1 ) ! f_i=\displaystyle\sum_{x=1}^{\frac{n}{2}}f_x\times f_{n-x}\times T\times \cfrac{(n-2)!}{(x-1)!(n-x-1)!} fi=x=1∑2nfx×fn−x×T×(x−1)!(n−x−1)!(n−2)!现在思考一下答案是什么。因为整体会有很多的环,所以每个环都要乘起来。因为答案中两个集合的顺序也可以打乱,设有 k k k个环,每个环的大小为 l i l_i li,所以答案为: a n s = ∏ i = 1 k f l i × ( n − k ) ! ∏ j = 1 k [ ( l j − 1 ) ! ] ans=\displaystyle\prod_{i=1}^kf_{l_i}\times\cfrac{(n-k)!}{\displaystyle\prod_{j=1}^k[(l_j-1)!]} ans=i=1∏kfli×j=1∏k[(lj−1)!](n−k)!
超时的处理
但是以上的状态转移方程算
f
f
f是
Θ
(
n
2
)
\Theta(n^2)
Θ(n2)的,会超时。但是我们可以先看一看
f
f
f数组算出来有什么特点:
1
1
3
16
125
1296
16807
262144
4782969
100000000...
1 1 3 16 125 1296 16807 262144 4782969 100000000...
1 1 3 16 125 1296 16807 262144 4782969 100000000...发现了什么?你没发现?放到
O
E
I
S
OEIS
OEIS上看看:
O
E
I
S
OEIS
OEIS告诉我们,这串数列是
n
n
−
2
n^{n-2}
nn−2。所以,
f
f
f数组的处理直接变成了
Θ
(
n
log
n
)
\Theta(n\log n)
Θ(nlogn)。
最后的问题:寻找环
因为本题只有简单环,直接 d f s dfs dfs就能很快地找到,时间复杂度只有 Θ ( n ) \Theta(n) Θ(n)。应该不会有人不会用 d f s dfs dfs找环吧?不会的详见代码
题目代码
#include<bits/stdc++.h>
using namespace std;
const int NN=1e5+4,P=1e9+9;
int f[NN],inv[NN],a[NN],l[NN];
bool vis[NN];
int qmi(int a,int b)
{
int res=1;
while(b)
{
if(b&1)
res=1ll*res*a%P;
a=1ll*a*a%P;
b>>=1;
}
return res;
}
int dfs(int u)
{
vis[u]=true;
if(vis[a[u]])
return 1;
return dfs(a[u])+1;
}
int main()
{
inv[0]=f[1]=1;
for(int i=1;i<NN;i++)
inv[i]=1ll*inv[i-1]*i%P;
for(int i=2;i<NN;i++)
f[i]=qmi(i,i-2);
int t;
scanf("%d",&t);
while(t--)
{
memset(vis,false,sizeof(vis));
int n,cnt=0;
scanf("%d",&n);
for(int i=1;i<=n;i++)
scanf("%d",&a[i]);
for(int i=1;i<=n;i++)
if(!vis[i])
l[++cnt]=dfs(i);
int ans=inv[n-cnt];
for(int i=1;i<=cnt;i++)
ans=1ll*ans*f[l[i]]%P*qmi(max(1,inv[l[i]-1]),P-2)%P;
printf("%d\n",ans);
}
return 0;
}