洛谷 AT_arc107_c [ARC107C] Shuffle Permutation 题解

题目链接

观察发现,两种操作是不会互相影响的。做了一次行交换之后,再做列交换时两列的任何对应数的和不变。所以我们考虑分别计算行交换和列交换的方案数再相乘。

下面以行为例讲解,列是同理的。可以二重循环枚举可以交换的两行,再一重循环判断是否可以交换。我们知道,当第 \(i\) 行和第 \(j\) 行可以交换,第 \(j\) 行和第 \(k\) 行可以交换时,第 \(i\) 行和第 \(k\) 行也可以交换。为了保证答案不重不漏,可以把一些所有能够互通的的行放到一个集合里,于是我们可以使用并查集来维护,最后计算方案数。

时间复杂度 \(O(n^3)\),记得做完行之后清空数组再计算列。

#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
typedef long long ll;
const int N=510;
const int MOD=998244353;
int n,m,a[N][N],fa[N],cnt[N];
ll fac[N]={0,1},ans=1;
bool check_hang(int x,int y)
{
    for(int i=1;i<=n;i++)
    {
        if(a[x][i]+a[y][i]>m) return false;
    }
    return true;
}
bool check_lie(int x,int y)
{
    for(int i=1;i<=n;i++)
    {
        if(a[i][x]+a[i][y]>m) return false;
    }
    return true;
}
int set_find(int x)
{
	return x==fa[x]?x:fa[x]=set_find(fa[x]);
}
void set_merge(int x,int y)
{
	int gx=set_find(x),gy=set_find(y);
	if(gx!=gy) fa[gx]=gy;
}
int main()
{
	memset(a,0x3f,sizeof a);
	scanf("%d%d",&n,&m);
	for(int i=2;i<=500;i++) fac[i]=(fac[i-1]*i)%MOD;
    for(int i=1;i<=n;i++) fa[i]=i;
	for(int i=1;i<=n;i++)
	{
		for(int j=1;j<=n;j++) scanf("%d",&a[i][j]);
	}
	for(int i=1;i<=n;i++)
	{
        for(int j=i+1;j<=n;j++)
		{
            if(check_hang(i,j)) set_merge(i,j);
        }
    }
    for(int i=1;i<=n;i++) cnt[set_find(i)]++;
    for(int i=1;i<=n;i++)
	{
		if(cnt[i]) ans=(ans*fac[cnt[i]])%MOD;
	}
	for(int i=1;i<=n;i++) fa[i]=i,cnt[i]=0;
	for(int i=1;i<=n;i++)
	{
        for(int j=i+1;j<=n;j++)
		{
            if(check_lie(i,j)) set_merge(i,j);
        }
    }
    for(int i=1;i<=n;i++) cnt[set_find(i)]++;
    for(int i=1;i<=n;i++)
	{
		if(cnt[i]) ans=(ans*fac[cnt[i]])%MOD;
	}
	printf("%lld\n",ans);
	return 0;
}
posted @ 2024-10-22 15:50  MinimumSpanningTree  阅读(4)  评论(0)    收藏  举报