zoukankan      html  css  js  c++  java
  • CH 3602 Counting Swaps

    给定一个 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)= egin{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=nsum_{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;
    }
    
  • 相关阅读:
    第四篇:new和delete的基本用法
    第三篇:C++ 中的几种初始化
    第七篇:使用 CUDA 进行计算优化的两种思路
    第六篇:二维数组的传输 (host <-> device)
    poj 2762(强连通+判断链)
    poj 3352(边双连通分量)
    poj 3228(二分+最大流)
    poj 3522(最小生成树应用)
    poj 2349(最小生成树应用)
    poj 1733(带权并查集+离散化)
  • 原文地址:https://www.cnblogs.com/zsyzlzy/p/12373930.html
Copyright © 2011-2022 走看看