zsyzlzy

导航

 

给定一个 1~n 的排列 p1,p2,,pnp_1,p_2,…,p_n,可进行若干次操作,每次选择两个整数 x,y,交换 px,pyp1,p2,,pnp_x,p_y。设把 p_1,p_2,…,p_n 变成单调递增的排列 1,2,…,n 至少需要 m 次交换。求有多少种操作方法可以只用 m 次交换达到上述目标。因为结果可能很大,你只需要输出对 10^9+9 取模之后的值。1≤n≤10^5。
例如排列 2,3,1 至少需要2次交换才能变为 1,2,3。操作方法共有3种,分别是:
先交换数字2,3,变成 3,2,1,再交换数字3,1,变成 1,2,3。
先交换数字2,1,变成 1,3,2,再交换数字3,2,变成 1,2,3。
先交换数字3,1,变成 2,1,3,再交换数字2,1,变成 1,2,3。

原理(算法):快速幂,乘法原理,乘法逆元,找规律。
我们令第i个点连一条有向边到pip_i,那么我们的目标其实就是得到n个自环。

T(x,y)T(x,y)表示把长度为n的环分为长度为x,y的环有多少种交换方法。容易发现,对于每个点,可以找到2个点来作为分界边的另一个点(当x≠y时),因为一条边被选两遍所以T(x,y)=n2/2=n.T(x,y)=n*2/2=n.另外,当x=yx=y时,那么分法会重复,所以T(x,y)=n/2.T(x,y)=n/2.
综上: T(x,y)={n/2(x=y)n(xy)T(x,y)= \begin{cases}\\n/2(x=y)\\n(x≠y)\end{cases}

引理:把一个长度为n的环变成n个自环最少需要n-1次交换操作。

证明:显然,每一次操作,最多只能将一个环变成两个环,所以将一个长度为n的环变成n个自环,最少也需要n-1次交换操作。

fnf_n为用最少步数,使得长度为n的环变成n个自环的方案数,那么:
fn=x+y=nT(x,y)fxfy(nk)!(x1)!(y1)!f_n=\sum_{x+y=n} T(x,y)*f_x*f_y* \dfrac{(n-k)!}{(x-1)!(y-1)!}
(nk)!(x1)!(y1)!\dfrac{(n-k)!}{(x-1)!(y-1)!}其实相当于任选处理长度为x,y的环的顺序的方案数。换句话说,如果处理长度为x的环为1,处理长度为y的环为0,那么上述表达式求的是1,0的(多重集的)排列数。那找这个规律,我们可以打出一个规模较小的表,发现fn=nn2f_n=n^{n-2}.

如果在n个点中,有长度为l1,l2,,lkl_1,l_2,……,l_k的k个环,i=1kli=n\sum_{i=1}^k l_i=n,那么
ans=fl1fl2fl3fl4flk(nk)!(l11)!(l21)!(lk1)!=(nk)!i=1kfli(li1)!ans=f_{l_1}*f_{l_2}*f_{l_3}*f_{l_4}*……f_{l_k}* \dfrac {(n-k)!}{(l_1-1)!*(l_2-1)!……(l_k-1)!}=(n-k)!*\prod_{i=1}^k \dfrac{f_{l_i}}{(l_i-1)!}
综上:我们只须初始化(li1)!fli(l_i-1)!的乘法逆元和f_{l_i}(快速幂O(NlogN)O(N log N)),就可以用O(N)O(N)的时间解决每一组数据。总时间复杂度O(NlogN)O(N log N)(gcd的复杂度不会证)

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
typedef long long ll;
const int N=1e5+10;
const int mod=1e9+9;
int n,a[N],l[N];bool vis[N];
ll jc[N],jc_inv[N],f[N],T,ans;
ll power_mod(ll a,ll b,ll c)
{
	ll ans=1%c;a%=c;
	while(b>0)
	{
		if(b&1)ans=ans*a%c;
		a=a*a%mod;b=b>>1;
	}
	return ans;
}
void dfs(int x)
{
	if(vis[x]){l[l[0]]++;return;}
	vis[x]=1;++l[0];dfs(a[x]);
}
#define g getchar()
template<class o>void qr(o &x)
{
	char c=g;x=0;
	while(!('0'<=c&&c<='9'))c=g;
	while('0'<=c&&c<='9')x=x*10+c-'0',c=g;
}
void write(ll x)
{
	if(x/10)write(x/10);
	putchar(x%10+'0');
}
int main()
{
	qr(T);
	jc[0]=jc_inv[0]=1;//0的阶乘等于1 
	for(int i=1;i<=100000;i++)
	{
		jc[i]=jc[i-1]*i%mod;
		jc_inv[i]=power_mod(jc[i],mod-2,mod);//求逆元 
		f[i]=power_mod(i,i-2,mod);
	}
	while(T--)
	{
		qr(n);
		memset(vis,0,sizeof(vis));
		memset(l,0,sizeof(l));
		for(int i=1;i<=n;i++)qr(a[i]);
		int cnt=0;//环的个数(包括自环) 
		for(int i=1;i<=n;i++)if(!vis[i])l[0]=0,++cnt,dfs(i);//找环的长度 
		ans=jc[n-cnt];
		for(int i=1;i<=n;i++)if(l[i])
		{
			n-=l[i]*i;//去掉l[i]*i个点 
			ans=ans*power_mod(f[i]*jc_inv[i-1],l[i],mod)%mod;//乘法结合律,小心溢出 
		}
		write(ans);puts("");
	}
	return 0;
}
posted on 2019-04-24 13:34  zsyzlzy  阅读(171)  评论(0编辑  收藏  举报