• 题解 P4778 【Counting swaps】


    Link

    P4778 Counting swaps

    Solve

    对于一个排列(p_1,p_2,...,p_n),如果从每个(i)(p_i)建一条边,显然可以得到一张图,这张图包括若干个环组成(包括自环)。最后的目标序列为(1,2,...,n),显然由(n)个自环组成。

    这里我们有一个引理:

    把一个长度为(n)的环变成(n)个自环,最少需要(n-1)次操作。

    证明:

    首先把长度为(2)的环变成(2)个自环,显然需要一种操作。

    假设(∀k≤n-1),把长度不超过(k)的环变成(k)个自环最少需要(k-1)次操作。当(k=n)时,设该环为(v_1→v_2→v_3→...→v_n→v_1),任意交换(v_i,v_j(i<j))的出边后,我们得到(v_{i+1}→v_{i+2}→v_{i+3}→...→v_j→v_{i+1})(v_{1}→v_{2}→v_{i}→...→v_{j+1}→v_{j+2}→...→v_n→v_1)两个环。

    两者的长度分别是(j-i)(n-(j-i))。把两者分别拆分成自环的最小交换次数为(j-i-1)(n-(j-i)-1)两者相加是(n-2),加上刚才(v_i)(v_j)的交换,总共需要(n-1)次交换。最后通过数学归纳法可知,原命题成立。

    证毕

    (F_n)表示用最少步数把一个长度为(n)的环变成(n)个自环,总共有多少中操作方法。由上面的证明过程可知,把一个长度为(n)的环变成(n)个自环的过程中,可以把该环拆成长度为(x)(y)的两个环,其中(x+y=n),设(T(x,y))表示有多少种交换方法可以把长度为(n)的环变成长度为(x)(y)的两个环,易得:

    [T(x,y)=egin{cases} {n/2,x=y}\{n,x≠y} end{cases}]

    另外,两者各自变为自环的方法数为(F_x)(F_y),步数为(x-1)(y-1)

    根据多重集的排列数·加法原理和乘法原理:

    [F_n=sum_{x+y=n}T(x,y)ast F_x ast F_y ast dfrac{(n-2)!}{(x-1)!(y-1)!} ]

    如果最初的排列(p_1,P-2,...,p_n)由长度为(l_1,l_2,...,l_k)(k)个环组成,其中(l_1+l_2+...l_k=n),那么最终答案就是

    [F_{l_1} ast F_{l_2} ast ... ast F_{l_k} ast dfrac{(n-k)!}{(l_1-1)! ast (l_2-1)! ast ... ast (l_k-1)!} ]

    因为(10^9+9)是一个质数,所以就可以用乘法逆元算上面的除法,整个算法的时间复杂度是(O(n^2))。事实上,我们可以递推出(F_n)的前几项找规律,数感好的同学应该可以找出规律,或者把前几项输入到(OEIS)网站中,我们都可以得到通项公式(F_n=n^{n-2}),从而可以用快速幂优化到(O(nlogn))

    代码不难,数学推理难。

    Code

    #include<iostream>
    #include<cstdio>
    #include<algorithm>
    #include<cstring>
    using namespace std;
    const int TT=1000000009,maxn=100005;
    int N,a[maxn],T,vis[maxn],cnt;
    typedef long long LL;
    LL ans,Fc[maxn],F[maxn],size[maxn];
    int read(){
    	int ret=0,f=1;char ch=getchar();
    	while(ch>'9'||ch<'0'){if(ch=='-')f=-f;ch=getchar();}
    	while(ch>='0'&&ch<='9') ret=ret*10+ch-'0',ch=getchar();
    	return ret*f; 
    }
    LL Pow(LL a,LL b){
    	LL w=a,s=1;
    	while(b){
    		if(b&1)s=w*s%TT;
    		w=w*w%TT;
    		b>>=1;
    	}
    	return s;
    }
    int main(){
    	freopen("P4778.in","r",stdin);
    	freopen("P4778.out","w",stdout);
    	T=read();
    	Fc[0]=1;for(int i=1;i<maxn;i++)Fc[i]=Fc[i-1]*i%TT;
    	F[1]=1;for(int i=2;i<maxn;i++)F[i]=Pow(i,i-2);
    	while(T--){
    		N=read();
    		for(int i=1;i<=N;i++)a[i]=read();
    		cnt=0;memset(vis,0,sizeof vis);
    		int now_x;
    		for(int i=1;i<=N;i++)if(!vis[i]){
    			now_x=i;int num=0;
    			while(!vis[now_x]){vis[now_x]=1;now_x=a[now_x];num++;};
    			size[++cnt]=num;
    		}
    		ans=Fc[N-cnt];
    		for(int i=1;i<=cnt;i++){
    			LL inv=Pow(Fc[size[i]-1],TT-2);
    			ans=ans*F[size[i]]%TT*inv%TT;
    		}
    		printf("%lld
    ",ans);
    	}
    	return 0;
    }
    
  • 相关阅读:
    错误 6 未能找到类型或命名空间名称“BLL”(是否缺少 using 指令或程序集引用?)
    linq 两个字段排序
    设置checkbox只读
    乐蜂网SALES倒计时原码展示
    Value cannot be null. Parameter name: source
    浅谈windows.onload()与$(document).ready()
    MVC之ViewData.Model
    td标签里内容不换行
    input type="image" 提交表单
    扩展Puppet – 建立Puppet CA集群
  • 原文地址:https://www.cnblogs.com/martian148/p/13860848.html
Copyright © 2020-2023  润新知