LZOI-二分图匹配例题

以下题目都是我们学校内部学生自己出的或改编的题目哦!

LZOI2206 同桌匹配

题目描述:初二(15)由于班主任觉得一些男生成绩太差,便安排他们班的某位无聊的班干做一件事。这位班干部需要给这些成绩差的男生分配一些能给予他学习动力的女同桌。于是,现在有n名同学(n<=1000),并且其中的一些女生能给予某些男生学习动力。现需要给这些男生配同桌,并且要求同桌数最大,请你输出最大的同桌数。
输入:第1行的2个数是n和m(m<=30000)。 接下来m行中,每行有2个正整数x和y,表示学号为x的男生与学号为y的女生可配为同桌。(数据保证任何一个x都不等于y)
输出:将求得的最多同桌数。

解析

是一道二分图匹配的模板题目。

代码
#include<bits/stdc++.h>//最基本的模板 
using namespace std;
int n,m,x,y,g[1001][1001],linker[1001],res;
bool used[1001];
int dfs(int x)
{
    for(int i=1;i<=n;i++)
    {
        if(used[i]==0 && g[x][i])
        {
            used[i]=1;
            if(linker[i]==0 || dfs(linker[i]))
            {
                linker[i]=x;
                linker[x]=i;
                return 1;
            }
        }
    }
    return 0;
}
int main()
{
    cin>>n>>m;
    for(int i=1;i<=m;i++)
    {
        cin>>x>>y;
        g[x][y]=1;
    }
    for(int i=1;i<=n;i++)linker[i]=0;
    for(int i=1;i<=n;i++)
    {
        for(int j=1;j<=n;j++)used[j]=0;
        if(dfs(i))res++;
    }
    cout<<res<<endl;
}

LZOI2207 宿舍

据说该题改编自洛谷2055 宿舍
题目描述:六中在春晚后总算是放假了 · · · · · · 有些同学回家了,而有些同学则有以前的好朋友来探访,那么住宿就是一个问题。比如 A 和 B 都是学校的学生,A 要回家,而 C 来看B,C 与 A 不认识。我们假设每个人只能睡和自己直接认识的人的床。(我就不信你敢睡你不认识的人的床)那么一个解决方案就是 B 睡 A 的床而 C 睡 B 的床。而实际情况可能非常复杂,有的人可能认识好多在校学生,在校学生之间也不一定都互相认识。我们已知一共有 n 个人,并且知道其中每个人是不是本校学生,也知道每个本校学生是否回家。问是否存在一个方案使得所有不回家的本校学生和来看他们的其他人都有地方住。
输入:第一行一个数 T 表示数据组数。接下来 T 组数据,每组数据第一行一个数n 表示涉及到的总人数。接下来一行 n 个数,第 i 个数表示第 i 个人是否是在校学生 (0 表示不是,1 表示是)。再接下来一行 n 个数,第 i 个数表示第 i 个人是否回家 (0 表示不会家,1 表示回家,注意如果第 i 个人不是在校学生,那么这个位置上的数是一个随机的数,你应该在读入以后忽略它)。接下来 n 行每行 n 个数,第 i 行第 j 个数表示 i 和 j 是否认识 (1 表示认识,0 表示不认识,第 i 行 i 个的值为 0,但是显然自己还是可以睡自己的床),认识的关系是相互的。
输出:对于每组数据,如果存在一个方案则输出 “ ^_^ ”(不含引号) 否则输出“T_T”(不含引号)。(注意输出的都是半角字符,即三个符号的 ASCII 码分别为94,84,95)

解析

这道题其实是让我们自己构建二分图,然后再套用模板做就行了(但是还是做了我好久)。

代码
#include<bits/stdc++.h>
using namespace std;
int T,n,g[111][111],zx[111],stu[111],bed[111],linker[111],used[111],ans;
//zx表示学生是否在校,stu表示二分图左边的学生,bed表示二分图右边的床。 
int dfs(int x)
{
    for(int i=1;i<=bed[0];i++)
        if(!used[bed[i]]&&g[bed[i]][x])
        {
            used[bed[i]]=1;
            if(!linker[bed[i]]||dfs(linker[bed[i]]))
            {
                linker[bed[i]]=x;
                return 1;
            }
        }
    return 0;
}
int main()
{
    cin>>T;
    while(T--)
    {
        memset(zx,0,sizeof(zx));//记得全部初始化 
        memset(stu,0,sizeof(stu));
        memset(bed,0,sizeof(bed));
        memset(linker,0,sizeof(linker));
        memset(g,0,sizeof(g));
        ans=0;
        cin>>n;
        for(int i=1;i<=n;i++)
            cin>>zx[i];
        for(int i=1;i<=n;i++)
        {
            int x;
            cin>>x;
            if(zx[i])//如果是在校学生 
            {
                if(x)bed[++bed[0]]=i;
                //如果不留校,那么床位增加。 
                else stu[++stu[0]]=bed[++bed[0]]=i;
                //如果留校,那么床位和学生都增加 
            }
            else stu[++stu[0]]=i;                                 
            //如果不是在校学生,那么学生增加(构建二分图的过程) 
        }
        for(int i=1;i<=n;i++)
            for(int j=1;j<=n;j++)
                cin>>g[i][j];
        for(int i=1;i<=n;i++)
            if(zx[i])g[i][i]=1;//别忘了自己也可以睡自己的床 
        for(int i=1;i<=stu[0];i++)//又回到了模板题                 
        {
            memset(used,0,sizeof(used));
            if(dfs(stu[i]))ans++;
        }    
        if(stu[0]>bed[0])ans=-1;                            
        //别忘了当床数小于学生数时,绝对无法完美匹配哦! 
        if(ans==stu[0])cout<<"^_^"<<endl;
        else cout<<"T_T"<<endl;
    }
    return 0;
}

LZOI2208 YZY的信封

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

解析

这道题目还是有一定难度的……思想大概是这样的:先dfs出一种完美匹配的方法。然后对于每一个点,删去这个点完美匹配的边,再次进行dfs。若不能完美匹配,则说明该点是肯定的。

代码
#include<bits/stdc++.h>
using namespace std;
bool g[111][111];
int used[111],xlinker[111],ylinker[111],x,y;
int n,ans=0;
int dfs(int x)
{
    for(int i=1;i<=n;i++)
        if(used[i]==0 && !g[x][i])
        {
            used[i]=1;
            if(!ylinker[i]||dfs(ylinker[i]))
            {
                ylinker[i]=x;
                xlinker[x]=i;
                return 1;
            }
        }
    return 0;
} 
int main()
{
    cin>>n;
    while(cin>>x>>y&&x&&y)g[x][y]=1;
    for(int i=1;i<=n;i++)//先进行一次dfs 
    {
        for(int j=1;j<=n;j++)used[j]=0;
        if(dfs(i))ans++;
    }
    if(ans!=n)cout<<"none"<<endl;//若本来就不能完美匹配,输出none 
    else
    {
        bool flag=false;
        for(int i=1;i<=n;i++)used[i]=0;
        for(int i=1;i<=n;i++)//对于每个点,删去它完美匹配的边 
        {
            int op=xlinker[i];
            g[i][op]=1;
            ylinker[op]=0;xlinker[i]=0;
            if(!dfs(i))                                   
        //若不能完美匹配,则说明该点可以肯定,输出该点和它连接的点 
            {
                cout<<i<<" "<<op<<endl;
                xlinker[i]=op;
                ylinker[op]=i;
                flag=true;
            }
            for(int j=1;j<=n;j++)used[j]=0;//不要忘记初始化 
            g[i][op]=0;
        }
        if(!flag)cout<<"none"<<endl;//若所有点都不能肯定,则输出none 
    }
}

关于二分图匹配的例题就没有了!下期是关于二分图判断哦!~

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值