NKOJ-4189 信与信封的问题<反向建图>

17 篇文章 0 订阅

P4189信与信封问题
时间限制 : - MS 空间限制 : 165536 KB
评测说明 : 1s

问题描述
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≤100)。信和信封依次编号为1,2,…,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

来源 FJOI 2001

首先我很好奇为什么John大晚上不睡觉去写信,还写了好几封,这是有多不走心

其次,小John为什么随便翻他爸爸的信,这个国家没点隐私权的么

题解

这道题目的解法很暴力

真的很暴力

建图

由于题目给的是否定的答案,但是我们通过一定的否定答案很难推导出肯定答案
所以我们就反向建图

哪条边是否定的,我们就删去这条边
而没有被提到的,就建起来

这样我们就得到了一个 某封信 可能装在 某个信封 的图

解题

首先,我们把信放在一遍,把信封放在一边,一个二分图就有了
其次,一个信封只能装一封信,那么装完信之后我们得到的集合就是一个配对
所以这道题目要用到最大匹配

易得:
假如某一个对应关系是必须的(即可以确定某一封信必须装进某一个信封),那么如果删去这条边,就不能找到一个配对使得每一封信都能够装进一个信封
但假如这个关系不是必须的,那么删掉了,每封信都还是可以在不冲突的情况下全部装进信封里头

也就是说,如果删去某条边之后,最大匹配的边数不为n(信的封数),那么这条边就是必须的,于是就可以输出了

所以我们只需要从小到大枚举每一条边,删掉这条边之后就跑一次最大匹配,如果最大匹配的边数不为n,那么这条边就是必须的

例如

有信1 2 3,信封4 5 6
1-5 2-5 3-5
1-4 2-4 3-4
1-6
那么如果删去1-6,得到的最大匹配无论如何无法达到3
所以1一定是装在6信封里头

但是其它的边管你怎么删,得到的最大匹配都是3
所以其它任意一条边都是必须的

注意

①删边是在原图上删去一条边,也就是说,删去下一条边的时候,这条边是存在的
②题目可能在一开始就是无解的,即一开始就无法保证能够把每一封信装进每一个信封,所以要先跑一次

#include <iostream>
#include <cstdio>
#include <cstring>
using namespace std;

int n,fath[123];
bool went[123],flag=0;
bool maps[123][123];

bool find(int s)
{
    for(int e=1;e<=n;e++)
        if(maps[s][e]&&!went[e])
        {
            went[e]=1;
            if(!fath[e]||find(fath[e]))
            {
                fath[e]=s;
                return 1;
            }
        }
    return 0;
}

int FD()
{
    memset(fath,0,sizeof(fath));
    int res=0;
    for(int i=1;i<=n;i++)
    {
        memset(went,0,sizeof(went));
        if(find(i))res++;
    }
    return res;
}

int main()
{
    int s,e;
    memset(maps,1,sizeof(maps));
    scanf("%d",&n);
    while(scanf("%d%d",&s,&e)!=EOF)maps[s][e]=0;
    if(FD()!=n){printf("none");return 0;}
    for(int s=1;s<=n;s++)
    for(int e=1;e<=n;e++)
        if(maps[s][e])
        {
            maps[s][e]=0;
            if(FD()!=n){printf("%d %d\n",s,e);maps[s][e]=1;flag=1;break;}
            maps[s][e]=1;
        }
    if(!flag)printf("none");
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值