2015年国家集训队测试 BZOJ3816矩阵变幻

Description

给出一个  N  行  M  列的矩阵A, 保证满足以下性质:

  1. M>N
  2. 矩阵中每个数都是  [0,N]  中的自然数。
  3. 每行中,  [1,N]  中每个自然数都恰好出现一次。这意味着每行中  0  恰好出现  MN  次。
  4. 每列中, [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.微笑

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值