Description
给定一个 2 n 2n 2n 个长度为 n n n 的排列,你需要从中选出 n n n 个组成一个拉丁方阵。
保证: 若对于每两个存在对应位置的值相同的排列连边,则此图有完美匹配。
1 ≤ n ≤ 500 1 \le n \le 500 1≤n≤500
Solution
我们刚拿到这个方阵,第一步该如何处理呢?
首先,如果某一列上,有一个数出现了恰好一次,那么该数所在的排列必然出现在最终的拉丁方阵里面。于是,选定它,然后将所有与它有连边的排列删掉。
其次,如果所有数的出现次数都超过一次呢?不难发现,根据抽屉原理(鸽巢原理),每个数的出现次数恰好为两次。这意味着什么呢?对于每一个方阵,包含它的拉丁方阵数量恰好等于不包含它的拉丁方阵数量。因此,我们可以随意从中挑出一个排列,删去与其有边直接相连的排列。注意也要将计数的答案乘 2 2 2。
现在,我们得到了每一列上有 n − 1 n-1 n−1 个数的子问题。我们似乎可以直接递归处理。然而,考虑这样的一种情况: 在上述的第一步中,我们删去了一个排列,并发现它没有出边。那么,在这一轮操作中,遗留的方阵有 2 n − 1 2n-1 2n−1 个,如果我们在某一列上找不到一个数出现恰好一次,那么就不一定所有数的出现次数均为 2 2 2,可能有某个数的出现次数为 3 3 3。这样,上述解法就无力回天了。
所以我们该怎么办呢?着眼于题目给出的奇怪限制: 图有完美匹配。这意味着什么呢?意味着,在第一轮操作中,选定的点一定有出边!遗留的排列数量不会超过 2 n − 2 2n-2 2n−2!于是,上面的情况不会出现,第二轮依然可以这么处理!
第三轮、第四轮 ⋯ ⋯ \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;
}