CF1545C AquaMoon and Permutations 题解

Description

给定一个 2 n 2n 2n 个长度为 n n n 的排列,你需要从中选出 n n n 个组成一个拉丁方阵。

保证: 若对于每两个存在对应位置的值相同的排列连边,则此图有完美匹配。

1 ≤ n ≤ 500 1 \le n \le 500 1n500

Solution

我们刚拿到这个方阵,第一步该如何处理呢?

首先,如果某一列上,有一个数出现了恰好一次,那么该数所在的排列必然出现在最终的拉丁方阵里面。于是,选定它,然后将所有与它有连边的排列删掉。

其次,如果所有数的出现次数都超过一次呢?不难发现,根据抽屉原理(鸽巢原理),每个数的出现次数恰好为两次。这意味着什么呢?对于每一个方阵,包含它的拉丁方阵数量恰好等于不包含它的拉丁方阵数量。因此,我们可以随意从中挑出一个排列,删去与其有边直接相连的排列。注意也要将计数的答案乘 2 2 2

现在,我们得到了每一列上有 n − 1 n-1 n1 个数的子问题。我们似乎可以直接递归处理。然而,考虑这样的一种情况: 在上述的第一步中,我们删去了一个排列,并发现它没有出边。那么,在这一轮操作中,遗留的方阵有 2 n − 1 2n-1 2n1 个,如果我们在某一列上找不到一个数出现恰好一次,那么就不一定所有数的出现次数均为 2 2 2,可能有某个数的出现次数为 3 3 3。这样,上述解法就无力回天了。

所以我们该怎么办呢?着眼于题目给出的奇怪限制: 图有完美匹配。这意味着什么呢?意味着,在第一轮操作中,选定的点一定有出边!遗留的排列数量不会超过 2 n − 2 2n-2 2n2!于是,上面的情况不会出现,第二轮依然可以这么处理!

第三轮、第四轮 ⋯ ⋯ \cdots \cdots 我们是不是都能这么干?我们果断猜想是可以的,但是我们需要一个强有力的证明。不难发现,我们要证明的东西就是: 对于一张 n n n 个点的,有完美匹配的图,每次在图上任意选出一个点,并将所有与它相邻的点删去,那么无论何时, n n n 减去已经结束的轮数的两倍不会超过剩余的点数。

注意到,对于任意 K K K,第 1 , 2 , 3 ⋯   , K 1,2,3\cdots,K 1,2,3,K 轮中删掉的总点数不小于 2 K 2K 2K。于是,上面的定理正确。

综上所述,我们每次判断是否有一列上存在某个数出现次数恰好为 1 1 1 次。如果存在的话,找到这个排列,将所有与它相邻的点以及它自己删去;如果不存在的话,将第一个答案乘 2 2 2,并随意钦定一个在最终拉丁方阵中的排列,将其与其相邻点删去。递归处理即可。

总复杂度 O ( n 3 ) O(n^3) O(n3),可以通过本题。

感觉讲的很清楚明白 ,当然可能是我太自恋了

Code

#include <bits/stdc++.h>
using namespace std;
const int maxl=1005,mod=998244353;

int read(){
    
    
	int s=0,w=1;char ch=getchar();
	while (ch<'0'||ch>'9'){
    
    if (ch=='-')  w=-w;ch=getchar();}
	while (ch>='0'&&ch<='9'){
    
    s=(s<<1)+(s<<3)+(ch^'0');ch=getchar();}
	return s*w;
}
int t,n,len,ans=1;
int a[maxl][maxl],used[maxl],ans2[maxl],tmp[maxl],pos[maxl];

void Choose(int now){
    
    
	used[now]=1,ans2[++len]=now;
	for (int i=1;i<=2*n;i++){
    
    
		if (i==now||used[i])  continue;
		
		int flag=0;
		for (int j=1;j<=n;j++){
    
    
			if (a[i][j]==a[now][j]){
    
    
				flag=1;
				break;
			}
		}
		if (flag)  used[i]=1;
	}
}

void solve(){
    
    
	int cur=0;
	for (int i=1;i<=n;i++){
    
    
		for (int j=1;j<=n;j++)  tmp[j]=0,pos[j]=0;
		for (int j=1;j<=2*n;j++){
    
    
			if (used[j])  continue;
			tmp[a[j][i]]++,pos[a[j][i]]=j;
		}
		for (int j=1;j<=n;j++){
    
    
			if (tmp[j]==1){
    
    
				cur=pos[j];
				break;
			}
		}
		if (cur)  break;
	}
	if (cur)  Choose(cur);
	else{
    
    
		ans=(ans*2)%mod;
		for (int i=1;i<=2*n;i++){
    
    
			if (!used[i]){
    
    
				Choose(i);
				break;
			}
		}
	}
}

void clear(){
    
    
	ans=1,len=0;
	memset(used,0,sizeof(used));
	memset(pos,0,sizeof(pos));
	memset(tmp,0,sizeof(tmp));
}

signed main(){
    
    
	t=read();
	while (t--){
    
    
		n=read();
		for (int i=1;i<=2*n;i++){
    
    
			for (int j=1;j<=n;j++)  a[i][j]=read();
		}
		for (int i=1;i<=n;i++)  solve();
		
		printf("%d\n",ans);
		sort(ans2+1,ans2+n+1);
		for (int i=1;i<=n;i++)  printf("%d ",ans2[i]);
		clear(),puts("");
	}
	return 0;
}

猜你喜欢

转载自blog.csdn.net/Cherrt/article/details/119697695