【清华集训2014】矩阵变换(稳定婚姻)
题意
给定一个 \(n\) 行 \(m\) 列的矩阵,满足:
- \(m>n\)。
- 矩阵中每个数都是 \([0,n]\) 内的整数。
- 每行中,\([1,n]\) 内每个整数恰好出现 \(1\) 次。这意味着 \(0\) 恰好出现 \(m-n\) 次。
- 每列中,\([1,n]\) 内每个整数出现不超过 \(1\) 次。
现在对于每行,我们要选取一个正数,然后把以它开头的后缀覆盖为它。我们希望保持最后一条性质。求一种合法方案。
\(T\le50,n\le200,m\le400\)。
分析
首先,操作完的矩阵中最后一列一定是排列,因此答案是 \(n\) 个行到 \(n\) 个数的完美匹配。
考虑一个匹配什么时候不合法。假设第 \(x\) 行匹配了数 \(i\)。若第 \(y\) 行的 \(i\) 未被覆盖且位于第 \(x\) 行的 \(i\) 右边,则会产生冲突。整理一下条件:
- 设第 \(a\) 行的 \(b\) 的位置为 \(\text{pos}_{a,b}\)。
- 原先的两对匹配为 \((x,i)\) 和 \((y,j)\)。
- \(\text{pos}_{y,i}<\text{pos}_{y,j}\)。
- \(\text{pos}_{y,i}>\text{pos}_{x,i}\)。
因此可以看成稳定婚姻问题。我们让每行偏好更靠左的数,每个数偏好它出现位置更靠右的行即可。
代码
#include<bits/stdc++.h>
#define rep(i,a,b) for(int i=a;i<=b;i++)
#define per(i,a,b) for(int i=a;i>=b;i--)
using namespace std;
const int N=205,M=405;
int T,n,m,a[N][M],pos[N][N],id[N][N],bk[N];
void aug(int i){
if(id[i][0]==n)exit(-1);
int x=id[i][++id[i][0]];
if(bk[x]){
int j=bk[x];bk[x]=0;
if(pos[x][i]>pos[x][j])swap(i,j);
bk[x]=i;aug(j);
}else bk[x]=i;
}
int main(){
scanf("%d",&T);
while(T--){
scanf("%d%d",&n,&m);
rep(i,1,n)rep(j,1,m){
scanf("%d",&a[i][j]);
if(a[i][j])pos[i][a[i][j]]=j;
}
rep(i,1,n){
iota(id[i],id[i]+n+1,0);
sort(id[i]+1,id[i]+n+1,[&](int x,int y){return pos[x][i]>pos[y][i];});
}
memset(bk,0,sizeof(bk));
rep(i,1,n)aug(i);
rep(i,1,n)printf("%d%c",bk[i]," \n"[i==n]);
}
return 0;
}