zoukankan      html  css  js  c++  java
  • 「刷题笔记」二分图匹配

    板子

    两种写法:(核心思想是一样的,使用时实测并没有什么区别

    两个参数版(洛谷题解

    bool dfs(ll u,ll ti)
    {
    	if(vis[u]==ti)return 0;
    	vis[u]=ti;
    	for(int i=head[u];i;i=next[i])
    	{
    		if(mh[e[i].v]==0||dfs(mh[e[i].v],ti))
    		{
    			mh[e[i].v]=u;
    			return 1;
    		}
    	}
    	return 0;
    }
    

    一个参数版(网上题解多用

    bool find(int u)
    {
    	for(int i=head[u];i;i=next[i])
    	{
    		int v=e[i].v;
    		if(!vis[v])
    		{
    			vis[v]=1;
    			if(mh[v]==0||find(mh[v]))
    			{
    				mh[v]=u;
    				return 1;
    			}
    		}
    	}
    	return 0;
    }
    

    特别注意:使用一个参数版时,每次使用前vis数组都要清零!

    超级英雄Hero

    啊这,这不是板子吗?
    然后就因为不读题+2了。
    建图:锦囊妙计编号是一边,题目编号是一边,从锦囊向题目连边,然后跑匈牙利
    注意题意:只有通过一题才能继续答题,所以如果中途断了直接跳出
    另外:二分图有重边似乎对匈牙利算法并没有影响

    文理分班

    首先建图,考虑到分班前后座位的变化,我们可以用前后座位建图,如果之前坐在(a)处的同学分班后有可能坐到(b)处,则从(a)(b)连边。
    同时注意,如果一个人是本班的,且不调班,那他是可以不换座位的,这样我们就直接从他的座位(a)(a)连边
    那么只要最后每个人都有座位坐就是可行的方案
    所以建图完了以后我们跑二分图匹配,如果全都能够匹配则可行

    放置机器人

    建图过程很有意思的一道题~
    可以分别考虑每一行,每一列,并将每行每列以墙分隔成几个小部分,那么显然,每一个小部分中是只能放置一个机器人的。
    我们又知道给定的行列信息能确定唯一位置,分隔后同样如此。我们可以把所有空地 按行分割所属部分 的编号向它们 按列分割所属部分 的编号连边,那么每一条边都代表了一个可放置机器人的位置,在得到的这个二分图上跑匈牙利,那么最大匹配边数即为最大能放置的机器人数。
    挺有意思的,放下代码

    #include<bits/stdc++.h>
    using namespace std;
    #define ll long long 
    #define ull unsigned long long
    #define ZZ_zuozhe int main()
    #define N 55
    #define E 2550
    
    struct edge
    {
    	ll u,v;
    }e[E];
    ll tot,head[E],next[E];
    void add(ll a,ll b)
    {
    	++tot;
    	e[tot].u=a;
    	e[tot].v=b;
    	next[tot]=head[a];
    	head[a]=tot;
    }
    
    ll T;
    ll n,m;
    ll tmp,xcn,ycn,xt;
    ll kd[N][N],xnm[N][N],ynm[N][N];
    
    ll in(ll &a)
    {
    	char ch=getchar();
    	if(ch=='#')return a=0;
    	else if(ch=='o')return a=1;
    	else if(ch=='*')return a=2;
    }
    
    ll vis[E],mh[E];
    
    bool find(ll u)
    {
    	for(int i=head[u];i;i=next[i])
    	{
    		ll v=e[i].v;
    		if(!vis[v])
    		{
    			vis[v]=1;
    			if(mh[v]==0||find(mh[v]))
    			{
    				mh[v]=u;
    				return 1;
    			}
    		}
    	}
    	return 0;
    }
    
    ll ans;
    
    ZZ_zuozhe
    {
    	memset(e,0,sizeof e);
    	memset(kd,0,sizeof kd);
    	memset(xnm,0,sizeof xnm);
    	memset(ynm,0,sizeof ynm);
    	memset(mh,0,sizeof mh);
    	tmp=xt=ans=0;
    	xcn=ycn=1;
    	scanf("%lld%lld",&n,&m);
    	getchar();
    	for(int i=1;i<=n;i++)
    	{
    		for(int j=1;j<=m;j++)
    		{
    			in(kd[i][j]);
    	//		cout<<kd[i][j]<<' ';
    			if(j==1&&tmp==1)tmp=0,xcn++;
    			if(kd[i][j]==1)xnm[i][j]=xcn,tmp=1,xt=xcn;
    			if(kd[i][j]==0&&tmp==1)tmp=0,xcn++;
    		}
    		getchar();
    	}
    	tmp=0;
    	for(int i=1;i<=m;i++)for(int j=1;j<=n;j++)
    	{
    		if(j==1&&tmp==1)tmp=0,ycn++;
    		if(kd[j][i]==1)ynm[j][i]=ycn,tmp=1;
    		if(kd[j][i]==0&&tmp==1)tmp=0,ycn++;
    	}
    	for(int i=1;i<=n;i++)for(int j=1;j<=m;j++)
    	{
    		add(xnm[i][j],ynm[i][j]);
    	}
    	for(int i=1;i<=xt;i++)
    	{
    		memset(vis,0,sizeof vis);
    		if(find(i))ans++;
    	}
    //	cout<<xcn<<' '<<ycn<<' '<<xt<<endl;
    	printf("%lld
    ",ans);
    	return 0;
    }
    

    猫和狗

    运用了最大独立集的思想
    关于最大独立集什么的:这篇博客写的超超超超详细啊qaq
    明确了定义,我们来看题awa
    题中,有人喜欢猫讨厌狗,有人喜欢狗讨厌猫,而且都是喜欢/讨厌特定的某只猫/狗,这个过程中,必然会产生冲突。
    那么,我们要使留下来的顾客尽量多,就必须不满足一些顾客的要求,进而消除冲突
    这个时候,我们可以这样建图:把喜欢猫的顾客归为一类,将喜欢狗的顾客归为一类,如果他们的需求出现了冲突,就将有冲突的两人连边。
    我们得到答案的过程是一个消除冲突的过程,那么这个过程中定会放弃部分顾客,用二分图的思想理解,就是说要把一些点抠掉,这样一些边就会消失。
    那么,我们的目的就转化成了将二分图上尽可能多的点留下,但要使任意留下的两点间没有连边。
    熟不熟?
    这不就最大独立集定义嘛!
    (König)定理:最大匹配(=)最小顶点覆盖
    (还不太会证这个东西,留一个坑
    又因为最大独立集(=)顶点个数(–)最小顶点覆盖(也就是最大匹配
    (为何?最大匹配中任意两条边都是没有公共点的,那就每条边抠掉一个点嘛
    所以,答案出来了,我们只要建图跑匈牙利,求出最大匹配,再用它减顶点个数(顶点个数是被减数)就行啦
    代码仅供参考

    #include<bits/stdc++.h>
    using namespace std;
    #define ll long long
    #define ull unsigned long long
    #define ZZ_zuozhe int main()
    #define N 5005
    #define E 90050
    #define K 5005
    
    struct edge
    {
    	ll u,v;	
    }e[E];
    ll tot=0,head[E],next[E];
    void add(ll a,ll b)
    {
    	++tot;
    	e[tot].u=a;
    	e[tot].v=b;
    	next[tot]=head[a];
    	head[a]=tot;
    }
    
    ll vis[N],mh[N];
    bool find(ll u)
    {
    	for(int i=head[u];i;i=next[i])
    	{
    		ll v=e[i].v;
    		if(!vis[v])
    		{
    			vis[v]=1;
    			if(!mh[v]||find(mh[v]))
    			{
    				mh[v]=u;
    				return 1;
    			}
    		}
    	}
    	return 0;
    }
    
    ll n,m,k;
    string s1,s2;
    ll n1,n2;
    ll ans=0;
    
    ll c[K],d[K],ln[K],ht[K];
    ll ccn=0,dcn=0;
    
    ZZ_zuozhe
    {
    	scanf("%lld%lld%lld",&n,&m,&k);
    	for(int i=1;i<=k;i++)
    	{
    		s1.clear(),s2.clear();
    		cin>>s1>>s2;
    		ll l1=s1.length(),l2=s2.length();
    		n1=n2=0;
    		for(int j=1;j<l1;j++)n1*=10,n1+=(ll)s1[j]-'0';
    		for(int j=1;j<l2;j++)n2*=10,n2+=(ll)s2[j]-'0';
    		if(s1[0]=='C')c[++ccn]=i;
    		else d[++dcn]=i;
    		ln[i]=n1;ht[i]=n2;
    	}
    	for(int i=1;i<=ccn;i++)for(int j=1;j<=dcn;j++)
    	{
    		if(ln[c[i]]==ht[d[j]]||ln[d[j]]==ht[c[i]])add(c[i],d[j]);
    	}
    	for(int i=1;i<=ccn;i++)
    	{
    		memset(vis,0,sizeof vis);
    		if(find(c[i]))ans++;
    	}
    	printf("%lld
    ",k-ans);
    	return 0;
    }
    

    Girls and Boys

    首先需要提一下这题的读入技巧
    他的输入文件给的花里胡哨的,我以前都是读到字符串里再暴力把数字提取出来……(时间爆炸)
    今天看到一个题解里是这样写的:

    scanf("%lld: ",&x);
    scanf("(%lld)",&tmp);
    

    啊这,还有这种操作???
    简直不要更方便了啊qaq
    (我是不是致远星了

    然后我们来看题目,题意挺清楚的就是一个最大独立集
    然后发现并不知道是男是女,所以二分图两边我们都用所有人的编号,给他求最大匹配,然后(n-ans/2)就好
    因为我们知道最大独立集(=n-)最大匹配
    诶?为啥是(/2)
    其实可以这样考虑:左右有男有女,我们把他们分开,男的放上面,女的放下面,因为不存在男男,也不存在女女,所以这个时候连完线,并求完最大匹配后整个图应该是对称的,而且不存在一条线同时经过了男部分和女部分,这个时候我们左边只留下男生,右边只留下女生,最大匹配就变成了原来的一半
    (多画点图就显然了
    其实代码跟上一题相似

    Treasure Exploration

    可相交最小路径覆盖。
    关于定义:看上面链接
    关于两种最小路径覆盖:某位dalao的csdn
    题意是很裸的,这里主要记一下两种最小路径覆盖解题过程的差别

    • 不相交:这个时候就是给每一条边起点终点连边,跑最大匹配
    • 相交:这个时候需要预处理每两个点是否联通,如果是则连边,这个时候就相当于不考虑中间经过的,而且起点和终点此时就都已经经过了,不会影响下一步的选择

    另外,为什么答案是(n-cnt)?其实,刚开始时,我们每个结点都要放一个机器人,而我们每确定一条要走的边,就可以少放一个(连通了嘛),所以当最大匹配时,我们要放的机器人数量是取最小值的

    再另外……
    数据太大,存图一定要手动清空QAQ,(memset())(O(n))的,如果你正好开了很大的数组就会直接gg
    如果嫌手动清空麻烦,用数组存边也是个不错的选择……(顺便还能把连通性判了)

    #include<cstdio>
    #include<cstring>
    #include<iostream>
    using namespace std;
    #define ll long long
    #define ull unsigned long long
    #define ZZ_zuozhe int main()
    #define N 1005
    #define E 1005
    
    ll n,m,a,b;
    ll mh[E],vis[E];
    bool mp[N][N]={0};
    bool find(ll u)
    {
    	for(int v=1;v<=n;v++)
    	{
    		if(!mp[u][v])continue;
    		if(!vis[v])
    		{
    			vis[v]=1;
    			if(!mh[v]||find(mh[v]))
    			{
    				mh[v]=u;
    				return 1;
    			}
    		}
    	}
    	return 0;
    }
    
    
    
    ll ans=0;
    
    ZZ_zuozhe
    {
    	while(scanf("%lld%lld",&n,&m)&&(n!=0||m!=0))
    	{
    		memset(mh,0,sizeof mh);
    		memset(mp,0,sizeof mp);
    		for(int i=1;i<=m;i++)
    		{
    			scanf("%lld%lld",&a,&b);
    			mp[a][b]=1;
    		}
    		for(int i=1;i<=n;i++)for(int j=1;j<=n;j++)for(int k=1;k<=n;k++)
    		{
    			mp[i][j]|=(mp[i][k]&mp[k][j]);
    		}
    		ans=0;
    		for(int i=1;i<=n;i++)
    		{
    			memset(vis,0,sizeof vis);
    			ans+=find(i);
    		}
    		printf("%lld
    ",n-ans);
    	}
    	return 0;
    }
    

    怎么说呢……这居然好像是我第一次没用链式前向星存边……也算第一次尝到苦头吧qaq以后最好多准备几种存边方式……

  • 相关阅读:
    哈希表,开放地址法之线性探测代码(JAVA)
    Java中几个关键字的意思
    java学习——File类操作
    Map三种遍历方法
    Spring Data Binding
    哈希表的基础理解
    选择排序
    map集合使用
    排序
    关于LinkedList
  • 原文地址:https://www.cnblogs.com/zzzuozhe-gjy/p/13382072.html
Copyright © 2011-2022 走看看