信与信封问题

来自codevs fzuoj

codevs
fzuoj
题目描述 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,尽可能多地将信正确地装回信封。

输入描述 Input Description
n文件的第一行是一个整数n(n≤100)。信和信封依次编号为1,2,…,n。

n接下来的各行中每行有2个数i和j,表示第i封信肯定不是装在第j个信封中。文件最后一行是2个0,表示结束。

输出描述 Output Description
输出文件的各行中每行有2个数i和j,表示第i封信肯定是装在第j个信封中。请按信的编号i从小到大顺序输出。若不能确定正确装入信封的任何信件,则输出“none”。

样例输入 Sample Input
3

1 2

1 3

2 1

0 0

样例输出 Sample Output
1 1

数据范围及提示 Data Size & Hint



这题用的方法是二分图匹配和反证法


建模的过程就是把信与信封的匹配抽象成x点集和y点集的二分图匹配,如果信i能放入信封j,就在xi和yj之间连一条边

直接判断一封信是否仅能放入一个信封很困难。于是用反证的方法,如果去掉一条从xi出发,到yj的边,就不可能找到包含xi的匹配,那么xi只能匹配yj。

找不到包含xi的匹配,就是说,从xi出发,找不到增广路

#include<cstdio>
#include<cstring>
#define maxn 110
#define rg register
using namespace std;
int ans=0,n,x,y,p[maxn],cx[maxn],cy[maxn];
bool v[maxn],g[maxn][maxn];
bool dfs(int t)
{
    if(t<=0) return false;
    for(int i=1;i<=n;i++)
    if(g[t][i]&&v[i]){
        int &p=i;
        v[p]=false;
        if(cy[p]==-1||dfs(cy[p])){
            cx[t]=p;
            cy[p]=t;
            return true;
        }
    }
    return 0;
}
int main()
{
    scanf("%d",&n);
    for(register int i=1;i<=n;i++){
        for(register int j=1;j<=n;j++){
            g[i][j]=true;
        }
    }
    while(scanf("%d%d",&x,&y)>0&&x!=0&&y!=0){
        g[x][y]=false;
    }
    memset(cx,-1,sizeof(cx));
    memset(cy,-1,sizeof(cy));
    for(register int i=1;i<=n;i++){
        if(cx[i]==-1){
            memset(v,true,sizeof(v));
            ans+=dfs(i);//二分图匹配,若都能匹配则可以 
        }
    }
    if(ans<n){//存在不能匹配的点
        printf("none\n");
        return 0;
    }
    bool flag=true;
    for(rg int i=1;i<=n;i++){
        int p=cx[i];
        g[i][p]=false;//将这条边删去 
        cx[i]=-1;
        cy[p]=-1;//将信从信封里取出来
        memset(v,true,sizeof(v));
        if(!dfs(i)){//如果不能匹配 
            printf("%d %d\n",i,p);
            flag=false;
            cx[i]=p;//把信放回去
            cy[p]=i;
        }
        //如果二分图找到了一个新的匹配,就不必再把信放回信封
        g[i][p]=true;//重新加上删除的边
    }
    if(flag) printf("none\n");
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值