Description
给出一个 N 行 M 列的矩阵A, 保证满足以下性质:
- M>N 。
- 矩阵中每个数都是 [0,N] 中的自然数。
- 每行中, [1,N] 中每个自然数都恰好出现一次。这意味着每行中 0 恰好出现 M−N 次。
- 每列中, [1,N] 中每个自然数至多出现一次。
现在我们要在每行中选取一个非零数,并把这个数之后的数赋值为这个数。我们希望保持上面的性质4,即每列中, [1,N] 中每个自然数仍然至多出现一次。
Input
第一行一个正整数 T ,表示数据组数。
后面包含 T 组数据,各组数据之间无空行。每组数据以两个正整数 N,M 开始,接下来 N 行,每行 M 个用空格隔开的整数,意义如题所述。
Output
对于每组数据输出一行。如果有解,则输出 N 个整数,依次表示每一行取的数是多少。(这应该是一个 1 到 N 的排列)如果无解,则输出任意卖萌表情。
题解:
0.9 这道题很巧妙的应用了“稳定婚姻问题”的模型。(传送门:http://www.matrix67.com/blog/archives/2976)
1. .显然是1~N的每行要与1~N的每一个自然数一一匹配,并将那一行对应德尔那个数后面的所有数变为他自己(好像婚姻匹配)。
2 .不合法的匹配即:一个自然数x在i行所匹配的数之前,并且x在它所匹配的行所匹配的位置比它在i行的位置靠前(即婚姻不稳定)。
3 .所以显然相对于自然数来说,它更喜欢自身出现位置更靠后(右)的行,对于行来说,它更喜欢位置靠前(左)的数字。
4. 那个是男那个是女呢?好像无所谓。。下面来讨论一下两种对应男女的对应做法:
4 - 1: 男数女行,对应到稳定婚姻的做法:每一轮,未匹配的数(单身狗)先选择(追求)还未被拒绝过得,自身出现位置更靠后(更喜欢)的行(女生)作为GF,若此行(此女)已有更靠前(此女更喜欢的男生)的数作为匹配(BF),则被拒绝等待下一轮,否则就追求成功(只是暂时,因为之后可能还会被别的数(男生)替换掉)。这样循环往复一轮一轮。。正确性详见通往matrix67的传送门。
4 -2:男行女数,对应到稳定婚姻的做法: 每一轮,未匹配的行先选择还未选择过的,出现最靠左的数字作为匹配对象,若此数已被其他行匹配且它在那个行的位置更靠后(右)那么这个求爱的行悲剧的继续等待下一轮,否则成功。。。道理同上。
5. 附上两种做法的代码(第一个是4-1,第二个是4-2 )
5.1 match[i]表示当前第i行与谁匹配,now[i]表示当前数字i该向第几喜欢的女生表白(或已成为couple),num[i]里存的是数字i在N行里的数据。
#include <iostream>
#include <algorithm>
#include <stdio.h>
#include <math.h>
#include <cstdlib>
#include <time.h>
#include<queue>
using namespace std;
const int maxn=210,maxm=420,maxt=60;
queue <int> Q;
int t,n,m,asdf,now[maxn];
int match[maxn],sz[maxn];
struct data{int hang,lie; }num[maxn][maxn];
bool cmp(data x,data y){ return x.lie>y.lie; }
int read(){
int ret=0; char c;
do{c=getchar();}while(c<'0'||c>'9');
while(c>='0'&&c<='9')ret*=10,ret+=c-'0',c=getchar();
return ret;
}
int main(){
scanf("%d",&t);
while(t--){
n=read(),m=read();
for(int i=1;i<=n;i++)match[i]=0,sz[i]=0;
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++){
asdf=read();
if(asdf)num[asdf][++sz[asdf]].hang=i,num[asdf][sz[asdf]].lie=j;
}
for(int i=1;i<=n;i++)sort(num[i]+1,num[i]+1+n,cmp);
for(int i=1;i<=n;i++)Q.push(i),now[i]=1;
while(!Q.empty()){
int x=Q.front();Q.pop();
if(!match[num[x][now[x]].hang])match[num[x][now[x]].hang]=x;//cout<<"success"<<endl;
else if(num[ match[num[x][now[x]].hang] ][ now[ match[num[x][now[x]].hang] ] ].lie>num[x][now[x]].lie)
Q.push(match[num[x][now[x]].hang]),now[ match[num[x][now[x]].hang] ]++,match[num[x][now[x]].hang]=x;//cout<<"win"<<endl;
else now[x]++,Q.push(x);//cout<<"fail"<<endl;
}
for(int i=1;i<=n;i++)
if(i<n)printf("%d ",match[i]);
else printf("%d\n",match[i]);
}
system("pause");
return 0;
}
5.2
(此处本应有说好的code...)准备写的时候突然发现。。好像并没有什么必要再写一遍。
上一段的代码与这里的代码的一些意义的代换:
数字 | 行号 |
行号 | 数字 |
列靠前好 | 列靠后好 |
That's all.Aha.