P4778 Counting swaps(图论+组合数)

题目链接:Counting swaps - 洛谷

给你一个1~n的排列p,可进行若干次操作,每次选择两个整数x,y,交换px,py,问用最少次数将给定排列变为单调上升的序列的方案数。

分析:这道题有一个基础题,是问将给定序列变为单调上升的序列的最少交换次数,我当时仔细分析了求最少交换次数的方法,详情看这里:交换瓶子(图论+思维)_AC__dream的博客-CSDN博客

就是我们把每个点与其最终位置连一条边,就按照这样的思路建完图后,会发现图中存在一些环,如果我们交换一个环内部的两个点,那么会把环一分为二,如果交换的两个点分别位于两个环中,那么会把原来的两个环合并为一个环,由于我们的最终序列是1,2,……,n,也就是说是n个长度为1的自环,显然一个长度为l的环至少经过l-1次可将其分解为l个长度为1的自环,所以可以求出给定图中环的个数,然后用n减去环的个数就得到了最少交换次数。

而这道题目要求出按照最少次数交换的方案数,设f[n]表示将一个长度为n的环分解为n个长度为1的环的方案数,T(x,y)表示将长度为x+y的环分解为两个长度分别为x和y的环的方案数

则显然有

T(x,y)=n/2(n为偶数)

T(x,y)=n(n为奇数)

而将长度为x和y的环分别分解为x和y个长度为1的环的最少交换次数分别为x-1,y-1,且两者是独立的,所以结合多重集的排列数就有

f[n]=(x+y=n)\sum T(x,y)*f[x]*f[y]*(n-2)!/(x-1)!/(y-1)!

但是利用这个方法算完可得f[n]=n^(n-2)

所以对于一开始给定图形里面的k个环,不妨设其长度分别为a1,a2,……,ak

则总的交换次数为n-k,每个环变为长度为1的自环所需的交换次数为a1-1,a2-1,……,ak-1

则最终答案就是f[a1]*f[a2]*……*f[ak]*(n-k)!/(a1-1)!/(a2-1)!/……/(ak-1)!

我们直接用f[n]=n^(n-2),然后再用逆元求出来(n-k)!/(a1-1)!/(a2-1)!/……/(ak-1)!对mod取余后的值相乘即可。

通过这道题目可以得到的两个结论(建议记住)

假设图中有k个环,那么将其变为长度为1的自环的最少交换次数为n-k

对于长度为l的环,将其变为长度为1的自环且交换次数最少的方案数为l^(l-2)

下面是代码:

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<iostream>
#include<queue>
using namespace std;
typedef long long ll;
const int N=1e5+10,mod=1e9+9;
bool vis[N];
int a[N],cnt[N];
ll fact[N],infact[N];
ll qpow(ll a,ll b)
{
	ll ans=1;
	while(b)
	{
		if(b&1) ans=ans*a%mod;
		a=a*a%mod;
		b>>=1;
	}
	return ans;
}
int dfs(int x)
{
	if(vis[x]) return 0;
	vis[x]=true;
	return dfs(a[x])+1;
}
void init()
{
	infact[0]=fact[0]=1;
	for(int i=1;i<=100000;i++)
	{
		fact[i]=fact[i-1]*i%mod;
		infact[i]=qpow(fact[i],mod-2);
	}
}
int main()
{
	int T,n;
	init();
	cin>>T;
	while(T--)
	{
		scanf("%d",&n);
		for(int i=1;i<=n;i++)
			scanf("%d",&a[i]),vis[i]=false;
		ll ans=1,count=0;
		for(int i=1;i<=n;i++)
			if(!vis[i])
			{
				cnt[++count]=dfs(i);
				ans=ans*qpow(cnt[count],(cnt[count]+mod-2)%mod)%mod;
				ans=ans*infact[cnt[count]-1]%mod;
			}
		ans=ans*fact[n-count]%mod; 
		printf("%lld\n",ans);
	}
	return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值