【清华集训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;
}
posted @ 2021-12-17 09:16  alfalfa_w  阅读(154)  评论(0编辑  收藏  举报