[codevs1222]信与信封问题(匈牙利)

来源:互联网 发布:淘宝账号注销后支付宝 编辑:程序博客网 时间:2024/06/05 09:30

题目描述 Description
John先生晚上写了n封信,并相应地写了n个信封将信装好,准备寄出。但是,第二天John的儿子Small John将这n封信都拿出了信封。不幸的是,Small John无法将拿出的信正确地装回信封中了。
将Small John所提供的n封信依次编号为1,2,…,n;且n个信封也依次编号为1,2,…,n。假定Small John能提供一组信息:第i封信肯定不是装在信封j中。请编程帮助Small John,尽可能多地将信正确地装回信封。
输入描述
n文件的第一行是一个整数n(n≤100)。信和信封依次编号为1,2,…,n。
n接下来的各行中每行有2个数i和j,表示第i封信肯定不是装在第j个信封中。文件最后一行是2个0,表示结束。
输出描述
输出文件的各行中每行有2个数i和j,表示第i封信肯定是装在第j个信封中。请按信的编号i从小到大顺序输出。若不能确定正确装入信封的任何信件,则输出“none”。
样例输入
3
1  2
1  3
2  1
0  0
样例输出
1   1

题意:告诉所有不能连的边,求剩下之中一定可以配对的边。看上去好像这道题有点诡异,其实就是匈牙利模板的一个变式。
思路:
输入:开了一个map数组,存的是可以相连的边(就是可能是配对的信与信封),map初值全部赋值为1,输入i,j的就赋值map[i,j]为0,表示i信不会在j中。
处理:先判断这个图是不是一个完美匹配(所有点都可以配对),如果不是完美匹配都可以直接输出none,
然后搜寻所有的边,删除当前存在的边,如果这条删了就不是完美匹配,说明,这条边的所连的i信是只有唯一的信封,即可以
确定此边存在,如果删了还是完美匹配,说明这条边只是可能,不能确定它一定存在的


代码如下
#include<cstdio>
#include<cstring>
#include<cmath>
#include<iostream>
#include<algorithm>
#include<cstdlib>
#define maxn 105
using namespace std;

int map[maxn][maxn];//数据范围比较小,直接开邻接矩阵

int num=0,vis[maxn],att[maxn];
int n,m;


int can(int x)
{
for(int i=1;i<=n;i++)
{
if(vis[i]==0&&map[x][i]==1)
{
vis[i]=1;
if(att[i]==0||can(att[i]))
{
att[i]=x;
return 1;
}
}
}
return 0;

}


int perfect()
{
int  ans=0;
for(int i=1;i<=n;i++)
{
memset(vis,0,sizeof(vis));
if(can(i))ans++;
}
memset(att,0,sizeof(att));
if(ans==n)return 1;
return 0;
}

int main()
{
scanf("%d",&n);
for(int i=1;i<=n;i++)
{
for(int j=1;j<=n;j++)
{
map[i][j]=1;
}
}
int a=1,b=1;
while(a!=0&&b!=0)
{
scanf("%d%d",&a,&b);
map[a][b]=0;
}
if(perfect()==0)printf("none");//不是完美匹配直接输出none
else{
for(int i=1;i<=n;i++)
{
for(int j=1;j<=n;j++)
{
if(map[i][j]==1)
{
map[i][j]=0;//删除边的操作
if(perfect()==0)//删除边就不是完美匹配,说明这条边是i的唯一边
{
map[i][j]=1;
num++;
printf("%d %d\n",i,j);//题目要求i从小到大输出,所以直接从小到大找即可
}
else
{
map[i][j]=1;
}
}

}
}


if(num==0)printf("none");


}
//noip水军一名,初次发文,如有不妥,请各位大佬指出