牛客图论--图匹配专题

情侣与聚餐


#include<bits/stdc++.h>
using namespace std;
#define endl '\n'
typedef long long ll; 
#define int long long
const int N=2e5+20;
int cnt,head[N],n;
int color[N];
struct node{
	int z,y;
}a[N];
struct edge{
	int to,nx;
};
edge ed[N*2];
void add(int a,int b){
	ed[++cnt].to=b;
	ed[cnt].nx=head[a];
	head[a]=cnt;
}
//典型二分图 染色法
void dfs(int u,int fa,int c){
	color[u]=c;
	for(int i=head[u];i!=-1;i=ed[i].nx){
		int j=ed[i].to;
		if(j==u) continue;
		if(color[j]) continue;
		dfs(j,u,3-c); 
	}
}
void solve()
{ 	cin>>n;
	for(int i=1;i<=2*n;i++) head[i]=-1;
	for(int i=1;i<=n;i++){
		int z,y;
		cin>>z>>y;
		a[i].z=z,a[i].y=y;
		add(z,y),add(y,z);//情侣之间建边
	}
	add(1,n),add(n,1);//圆桌
/*
 1 2 3 4 5 6
如果1<->3,3<->5  1与5是情侣,则不行
*/	
	for(int i=1;i<=2*n;i+=2){
//连续三个人不能一样
//连续两两相连,咋每连续三个强制有一个不一样,可以
		add(i,i+1),add(i+1,i);
	}
	for(int i=1;i<=2*n;i++){
		if(!color[i])
		dfs(i,-1,1);	
	}
	 for(int i=1;i<=n; i++) {
    cout<<color[a[i].z]<<" "<<color[a[i].y]<<endl;
    }
}
signed main()
{
	ios::sync_with_stdio(false);
cin.tie(0),cout.tie(0);

	solve();

	return 0;
}

假期的宿舍

#include<bits/stdc++.h>
using namespace std;
#define endl '\n'
typedef long long ll; 
#define int long long
const int N=100000;
int cnt,head[N];
int n;
int zai[N],zhu[N],st[N],match[N];
struct edge
{
	int to,nx;
};
edge ed[N];
void add(int a,int b)
{
	ed[++cnt].to=b;
	ed[cnt].nx=head[a];
	head[a]=cnt;
}
int ans=0,res=0;
bool find(int x)
{ 
    for(int i=head[x];i!=-1;i=ed[i].nx) 
    { 
        int j=ed[i].to; 
        if(!st[j])
        { 
            st[j]=true;

            if((match[j]==0)||find(match[j])) 
            {
                match[j]=x;
                return true;
            }
        }
    }
    return false;
}
void solve()
{	cnt=0;
	cin>>n;
	res=0,ans=0;
	for(int i=1;i<=n;i++) {head[i]=-1;match[i]=0;}
	for(int i=1;i<=n;i++) cin>>zai[i];
	for(int i=1;i<=n;i++){
		cin>>zhu[i];
		if(zhu[i]==0) zhu[i]=1;
		else zhu[i]=0;
		if(!zai[i]) zhu[i]=1;
		if(zhu[i]) res++;
	}
	for(int i=1;i<=n;i++){
	if(zai[i]&&zhu[i]) add(i,i);//自己是本校,而且住学校,那么自己和自己的床建边
	for(int j=1;j<=n;j++){
		int s;cin>>s;
		if(s==1&&zai[j]) {
			add(i,j);//自己的朋友j是本校,那么自己和朋友床建边
		}
	}
}
	for(int i=1;i<=n;i++){
		if(!zhu[i]) continue;
		for(int j=1;j<=n;j++) st[j]=0;
		if(find(i)) {ans++;}
	}
	if(res==ans){cout<<"^_^\n";}
	else cout<<"T_T\n";
}
signed main()
{
	ios::sync_with_stdio(false);
cin.tie(0),cout.tie(0);
	int t; cin>>t;
	while(t--)
{
	solve();
}
	
	
	return 0;
}

KM最大权匹配

#pragma GCC optimize(3,"Ofast","inline")
#pragma comment(linker, "/STACK:1024000000,1024000000")
#include<bits/stdc++.h>
using namespace std;
#define endl '\n'
int p,h;//左边点,右边点
const int N=200;
const int INF=999999999;
int a[N][3];//左边点坐标x,y;
int b[N][3];//右边点坐标x,y;
int w[N][N];//俩点距离
int match[N];//右点匹配了那个左点
int lx[N],ly[N];//左顶点,右顶点
bool visx[N],visy[N];//左右点是否在交替路
bool find(int u)//左边点
{
	visx[u]=1;//u在交替路中
	for(int i=1;i<=h;i++)//遍历右边点
	{   //相等子图
		if(!visy[i]&&lx[u]+ly[i]==w[u][i])
		{
			visy[i]=1;//i在交替路中
			if(match[i]==-1||find(match[i]))
			{
				match[i]=u;
				return 1;
			}
		}
	}
	return 0;
}
/*
最优匹配是建立在完美匹配的基础上的,如果不存在完美匹配,那么本算法
失效(但是,我们可以人为连一些权值0的边,甚至加点,使得没有匹配的节点们最后都有
一个"虚假"的匹配)
KM求二分图最大权匹配
如果求最小权匹配
那么初始化边W为-W,
最后最大权就是最小权
*/
int km()
{	for(int i=1;i<=h;i++) match[i]=-1;
	//memset(match,-1,sizeof match);
	for(int i=1;i<=p;i++)//左点初始化负无穷
		lx[i]=-INF;
	for(int i=1;i<=h;i++)//右点0
		ly[i]=0;
	for(int i=1;i<=p;i++)
	for(int j=1;j<=h;j++)
	//左点初始化最值
	lx[i]=max(lx[i],w[i][j]);
	for(int i=1;i<=p;i++)
	{
		while(1)
		{	
			for(int i=1;i<=p;i++) visx[i]=false;
			for(int i=1;i<=h;i++) visy[i]=false;
			if(find(i))break;
			int d=INF;
			for(int j=1;j<=p;j++)
				if(visx[j])//左边在交替路
				for(int k=1;k<=h;k++)
				if(!visy[k])d=min(d,lx[j]+ly[k]-w[j][k]);
			if(d==INF) return -1;//不能匹配	
			for(int j=1;j<=p;j++)if(visx[j])lx[j]-=d;
			for(int j=1;j<=h;j++)if(visy[j])ly[j]+=d;
		}
	}
	int ans=0;
	for(int i=1;i<=h;i++)ans+=w[match[i]][i];
	return -ans;
}
signed main()
{
	ios::sync_with_stdio(false); 
	cin.tie(0);cout.tie(0);
 	int n,m;
	while(cin>>n>>m)
	{
		p=0,h=0;
		if(n==0&&m==0)break;
		for(int i=1;i<=n;i++)
			for(int j=1;j<=m;j++)
			{
				char op;cin>>op;
				if(op=='m')a[++p][0]=i,a[p][1]=j;
				else if(op=='H')b[++h][0]=i,b[h][1]=j;
			}
		for(int i=1;i<=p;i++)
		for(int j=1;j<=h;j++)
		w[i][j]=-(abs(a[i][0]-b[j][0])+abs(a[i][1]-b[j][1]));
		cout<<km()<<'\n';
	}
	return 0;
 
}

最小点覆盖==最大匹配

每条边是障碍物,任意一个左或右点,则与两个点有关的边全被覆盖

相当于选择最少数量的左或右点,覆盖全部边==二分图最大匹配

最小点覆盖

#include<bits/stdc++.h>
using namespace std;
#define endl '\n'
typedef long long ll; 
#define int long long
#define ld  double
const int N=10100;
int n,m;
int g[N][N],match[N];
pair<int,int> num[N];
bool st[N],flag[N];

bool find(int u)
{//匈牙利算法
	for(int i=1;i<=n;i++)
	{	
		if(g[u][i]==0||st[i])continue;
		//如果有边,并且没有匹配则继续搜索
		st[i]=1;
		if(match[i]==-1||find(match[i]))
		{
			match[i]=u;
			return 1;
		}
	}
	return 0;
}
signed main()
{
	ios::sync_with_stdio(false); 
	cin.tie(0);cout.tie(0);
	memset(match,-1,sizeof match);
	cin>>n>>m;
	for(int i=1;i<=m;i++)
	{
		int a,b;cin>>a>>b;
		g[a][b]=1;//建边
		num[i].first=a,num[i].second=b;
	}
	int cnt=0;
	for(int i=1;i<=m;i++)
	{
		memset(st,0,sizeof st);
		int x=num[i].first;
		if(flag[x])continue;
		if(find(x))cnt++;
		flag[x]=1;
	}
	
	cout<<cnt<<'\n';
	
}

矩阵游戏

行: 左点        列:右点

如果每一行,每一列都至少有一个黑格,那么一定成立.

如果该点有黑格,那么行与列建有向边.  直接进行二分图最大匹配

如果 最大匹配数==行或者列(n),则一定成立

#include<bits/stdc++.h>
using namespace std;
#define endl '\n'
typedef long long ll; 
#define int long long
#define ld  double
const int N=300;
int n,m;
int g[N][N],match[N];
pair<int,int> num[N];
bool st[N],flag[N];
bool find(int u)
{//匈牙利算法
	for(int i=1;i<=n;i++)
	{	
		if(g[u][i]==0||st[i])continue;
		//如果有边,并且没有匹配则继续搜索
		st[i]=1;
		if(match[i]==-1||find(match[i]))
		{
			match[i]=u;
			return 1;
		}
	}
	return 0;
}
void solve(){
	 cin>>n;
	for(int i=1;i<=n;i++) match[i]=-1;
	for(int i=1;i<=n;i++)
	for(int j=1;j<=n;j++)
		cin>>g[i][j];
		int cnt=0;
		for(int i=1;i<=n;i++)
	{
		memset(st,0,sizeof st);
		if(find(i))cnt++;
	}
	if(cnt==n) cout<<"Yes\n";
	else cout<<"No\n";
}
signed main()
{
	ios::sync_with_stdio(false); 
	cin.tie(0);cout.tie(0);
	int t;cin>>t;
	while(t--) {
		solve();
	}
}

黑白匹配

2*1的格子,一个当黑色格子,相邻就是白色格子;

所有的白色格子与黑色格子连接一条边。形成了一个二分图

二分图最大匹配就是最大棋盘覆盖数

#pragma GCC optimize(3,"Ofast","inline")
#pragma comment(linker, "/STACK:1024000000,1024000000")
#include<bits/stdc++.h>
using namespace std;
#define endl '\n'
const int N=300;
typedef pair<int,int> pii;
pii match[N][N];
int n,t;
int dx[]={-1,0,1,0},dy[]={0,1,0,-1};
bool st[N][N],g[N][N];
bool find(int x,int y)
{
	for(int i=0;i<4;i++)
	{
		int a=x+dx[i],b=y+dy[i];
		if(a<1||a>n||b<1||b>n||g[a][b]||st[a][b])continue;
		st[a][b]=1;
		pii t=match[a][b];
		
		//t.first没有男朋友,或者男朋友可以再和别人配对
		if(t.first==0||find(t.first,t.second))	 
		{
			match[a][b]={x,y};
			return 1;
		}		
	}
	return 0;
}
void solve(){
		cin>>n;
	for(int i=1;i<=t;i++)
	{
		int x,y;
		cin>>x>>y;
		g[x][y]=1;  //被损坏的点
	}
	int res=0;
	for(int i=1;i<=n;i++)
		for(int j=1;j<=n;j++)
			if(!g[i][j]&&(i+j)%2)//偶数点是男朋友,奇数点是女朋友(自定义的 
			{
				memset(st,0,sizeof st);
				if(find(i,j))res++;
			}
   cout<<res<<endl;
}
signed main()
{
	  int t;cin>>t;
	  while(t--) solve();
	return 0;
}

画圈游戏

2*1的格子,一个当黑色格子,相邻就是白色格子;

所有的白色格子与黑色格子连接一条边。形成了一个二分图

二分图最大匹配就是最大棋盘覆盖数

最后匹配数量完成的是2*res个格子;

还剩下 ANS-2*res个格子没有匹配

因此答案就是ans-2*res+res==ans-res 

#pragma GCC optimize(3,"Ofast","inline")
#pragma comment(linker, "/STACK:1024000000,1024000000")
#include<bits/stdc++.h>
using namespace std;
#define endl '\n'
const int N=300;
typedef pair<int,int> pii;
pii match[N][N];//这个点的男朋友
int n,t,m;
int dx[]={-1,0,1,0},dy[]={0,1,0,-1};
bool st[N][N],g[N][N];
bool find(int x,int y)
{
	for(int i=0;i<4;i++)
	{
		int a=x+dx[i],b=y+dy[i];
		if(a<1||a>n||b<1||b>m||g[a][b]||st[a][b])continue;
		st[a][b]=1;
		pii t=match[a][b];
		//t.first没有男朋友,或者男朋友可以再和别人配对
		if(t.first==0||find(t.first,t.second))	 
		{
			match[a][b]={x,y};
			return 1;
		}		
	}
	return 0;
}
int main()
{
	cin>>n>>m;
    int ans=0;
	for(int i=1;i<=n;i++)
	{
         string s;
         cin>>s;
        for(int j=0;j<s.size();j++) {
            if(s[j]!='*'){
                g[i][j+1]=1;//该点不可行
            }else ans++;
        }
	}
	int res=0;
	for(int i=1;i<=n;i++)
		for(int j=1;j<=m;j++)
			if(!g[i][j]&&(i+j)%2)//偶数点是男朋友,奇数点是女朋友(自定义的 
			{
				memset(st,0,sizeof st);
				if(find(i,j))res++;
			}
 	cout<<ans-res;
	return 0;
}

占领城市

左边点是出点(出度的点), 右边是入点(入度的点)。

左边与右边的二分图最大匹配数量

所有点的数量-最大匹配数==结果

不重复路径最大匹配数

如果要求重复路径最大匹配数,则a->b->c->d情况下,

a->b,a->c,b->d,a->d,全部建边,然后求最大匹配数

所有点的数量-最大匹配数==结果

#pragma GCC optimize(3,"Ofast","inline")
#pragma comment(linker, "/STACK:1024000000,1024000000")
#include<bits/stdc++.h>
using namespace std;
#define endl '\n'
typedef long long ll; 
#define int long long
#define ld long double
int n,m;
const int N=510;
vector<int>z[N];
int st[N],match[N];
bool find(int x){
	for(int i=0;i<z[x].size();i++){
		int j=z[x][i];
		if(st[j]) continue;
		st[j]=1;
		if(match[j]==0||find(match[j])){
			match[j]=x;
			return 1;
		}
	}
	return 0;
}
void solve()
{
	 cin>>n>>m;
	 while(m--){
	 	int a,b;
	 	cin>>a>>b;
		//出度后面跟着入度点
	 	z[a].push_back(b);
	 }
	int res=0;
	for(int i=1;i<=n;i++){
		memset(st,false,sizeof(st));
		if(find(i)) res++;
	}
	cout<<n-res;
}
signed main()
{
	ios::sync_with_stdio(false);
cin.tie(0),cout.tie(0);


	solve();

	
	
	return 0;
}

中心图

算法分析:读完题可以感受到这个中心点的重要性,所以我们需要先分析这个中心点怎么处理(其实,我看到这里想到了枚举,但是感觉很麻烦,其实一点也不麻烦,要耐下心来分析,别直接溜了!)因为总点数只有五百个,这也暗示了我们要把中心点给先求出来,然后再进行第三个要求“出入度均为2”。那么中心点我们可以通过枚举的方式求得,有了中心点,操作3就可以转换成除了和中心点连的边,其他的出入度必须为1。出入度为1,细品一下,就是把一个点拆成出点和入点,分别在二分图的两侧,然后进行最大匹配。细节如下。

  1. 先保证满足操作2的要求,我们要把所有不满足操作2的点和中心点进行连边(不用真的连边,记个数字就可以了),注意中心点自己还有和自己连边,这个需要特判一下,因为有的输入给了中心点自环,有的没给,需要建边。
  2. 删边操作:除了中心点外的其他点拆成出点、入点,进行二分图最大匹配。那么删除的边就是没用的边,这里求没用的边的个数等于总边数-数据中与中心点有关的边数-最大匹配的边数。
  3. 添加操作:对于一些孤立点,我们可以让他们自己和自己连边,那么需要操作的次数就是总点数-二分图最大匹配的点数-1(这个1是中心点).
#include<bits/stdc++.h>
using namespace std;
#define endl '\n'
typedef long long ll; 
#define int long long
#define ld  double
const int N=510;
int n,m;
int g[N][N],match[N];
bool st[N];
bool find(int u,int x)
{
	for(int i=1;i<=n;i++)
	{
		if(i==x)continue;
		if(!g[u][i]||st[i])continue;
		st[i]=1;
		if(match[i]==-1||find(match[i],x))
		{
			match[i]=u;
			return 1;
		}
	}
	return 0;
}
int solve(int x)
{
	memset(match,-1,sizeof match);
	int sum=0;		//sum表示为了满足条件2需要加的边数
	for(int i=1;i<=n;i++)
	{
		if(i==x&&!g[x][i])
		{
			sum++;
			continue;
		}
		
		if(!g[x][i])sum++;
		if(!g[i][x])sum++;
	} 
	int cnt=0;//n-1个节点(除了中心点x)的最大匹配
	for(int i=1;i<=n;i++)
	{
		if(i==x)continue;
		memset(st,0,sizeof st);
		if(find(i,x))cnt++;
	} 
	int sub;		
	//sub表示把图中进行最大匹配后剩余的无用边给删掉
	sub=m-(2*n-1-sum)-cnt;		
	//第二项表示的是题中所给的与中心点有关的边(而不是操作2所加的边
	int add=n-1-cnt;	//add表示对孤寡点自己和自己相连
   // cout<<"--"<<add<<" "<<sub<<" "<<sum<<endl;
	return add+sub+sum; 
}
signed main()
{
	ios::sync_with_stdio(false); 
	cin.tie(0);cout.tie(0);
	cin>>n>>m;
	for(int i=1;i<=m;i++)
	{
		int u,v;cin>>u>>v;
		g[u][v]=1;
	} 
	int res=0x3f3f3f3f;
	for(int i=1;i<=n;i++)
	{
		res=min(res,solve(i));
	}
	cout<<res<<'\n';

	return 0;
}

新节点二分匹配

算法分析:如果抛开插线板不谈,那么这就是个二分图求最大匹配的板子题。那加上插线板,我们就可以枚举所有的插座,分析如果把插线板放这里,是否能再增加几个电器被连接。但是我们不能在进行完没有插线板的情况下的最大匹配之后枚举每个插座还没有连接的电器数量,然后得到插线板应该放哪,因为可能你的插线板把这个电器给改变了,会导致其他的插座空了出来。因此我们可以直接在原有的二分图上增加两个插座(表示插线板),通过在原有的最大匹配基础上,我们进行点n+1、点n+2(表示插线板)是否能匹配到电器。那么答案就是没有插线板时的最大匹配数加上有了插线板之后能够匹配到电器的最大数。

小技巧:可以用bitset来代替bool类型的st数组,这样时间复杂度更小。

#include <bits/stdc++.h>
using namespace std;
const int N=2e3,M=1e6;
typedef long long ll;
int head[N],cnt;
struct edge
{
	int to,nx;
};
edge ed[M];
inline void add(int a,int b)
{
	ed[++cnt].to=b;
	ed[cnt].nx=head[a];
	head[a]=cnt;
}
int match[N],fmatch[N];
bitset<N>st;
inline bool find(int x,int match[])
{
	for(int i=head[x];i!=-1;i=ed[i].nx)
	{
		int j=ed[i].to;
		if(st[j])continue;
        st[j]=1;
		if(match[j]==-1||find(match[j],match))
		{
			match[j]=x;
			return 1;
		}
	}
	return 0;
}
void solve(){
   memset(head,-1,sizeof head);

    memset(match,-1,sizeof match);
	cnt=0;
	int n,m,k;
	cin>>n>>m>>k;
	for(int i=1;i<=n;i++) head[i]=-1;
	for(int i=1;i<=k;i++)
	{
		int u,v;cin>>u>>v;
		add(u,v);
	}
	int res=0;
	for(int i=1;i<=n;i++)
	{
		st.reset();
		if(find(i,match))res++;
	}
	int sum=-1,biao=cnt;
	
	for(int i=1;i<=n;i++)
	{  //保留原本的二分匹配
		memcpy(fmatch,match,sizeof match);
		head[n+1]=head[n+2]=-1;
		//建立边的下标
		cnt=biao;
		int num=0;
		for(int j=head[i];j!=-1;j=ed[j].nx)
			add(n+1,ed[j].to);
		  st.reset();
		if(find(n+1,fmatch))num++;
        
        for(int j=head[i];j!=-1;j=ed[j].nx)
            add(n+2,ed[j].to);
		st.reset();
		if(find(n+2,fmatch))num++;
		sum=max(sum,num);
	}
	cout<<res+sum<<'\n'; 
}
signed main()
{
	ios::sync_with_stdio(false); 
    cin.tie(0);cout.tie(0);
    solve();
	return 0;

}

最优多重二分匹配

题意:
给 一 个 没 有 重 边 的 二 分 图 , 要 求 给 边 染 色 给一个没有重边的二分图, 要求给边染色给一个没有重边的二分图,要求给边染色
有 公 共 点 的 边 不 能 同 色 . 问 最 少 用 多 少 种 颜 色 有公共点的边不能同色. 问最少用多少种颜色有公共点的边不能同色.问最少用多少种颜色
并 任 意 构 造 一 组 方 案 . 并任意构造一组方案.并任意构造一组方案.

题解:
n < = 1 e 3 , m < = 2 e 3 n <=1e3,m<=2e3n<=1e3,m<=2e3
题 目 要 求 转 化 一 下 , 就 是 从 一 个 点 出 入 的 所 有 边 , 颜 色 必 须 不 同 题目要求转化一下,就是从一个点出入的所有边,颜色必须不同题目要求转化一下,就是从一个点出入的所有边,颜色必须不同
但 是 从 另 一 个 点 出 入 的 边 , 可 能 与 这 些 颜 色 相 同 , 可 能 不 同 但是从另一个点出入的边,可能与这些颜色相同,可能不同但是从另一个点出入的边,可能与这些颜色相同,可能不同
使 用 最 少 颜 色 , 就 是 使 得 这 些 颜 色 尽 量 相 同 的 多 一 些 使用最少颜色,就是使得这些颜色尽量相同的多一些使用最少颜色,就是使得这些颜色尽量相同的多一些

所 以 , 每 次 找 到 一 条 边 , 他 的 两 个 端 点 在 这 次 查 找 都 没 出 现 过 所以,每次找到一条边,他的两个端点在这次查找都没出现过所以,每次找到一条边,他的两个端点在这次查找都没出现过
这 样 找 出 的 一 组 边 就 可 以 用 相 同 颜 色 这样找出的一组边就可以用相同颜色这样找出的一组边就可以用相同颜色
并 且 这 是 一 个 二 分 图 , 所 以 就 成 了 二 分 图 的 最 大 匹 配 问 题 并且这是一个二分图,所以就成了二分图的最大匹配问题并且这是一个二分图,所以就成了二分图的最大匹配问题
多 次 调 用 二 分 图 最 大 匹 配 , 为 每 次 匹 配 出 来 的 边 赋 上 颜 色 , 直 到 全 部 匹 配 多次调用二分图最大匹配,为每次匹配出来的边赋上颜色,直到全部匹配多次调用二分图最大匹配,为每次匹配出来的边赋上颜色,直到全部匹配

但 是 光 这 样 肯 定 还 是 无 法 到 达 最 优 的 但是光这样肯定还是无法到达最优的但是光这样肯定还是无法到达最优的
再 想 一 下 , 其 实 每 个 点 出 入 的 所 有 边 颜 色 一 定 不 同 再想一下,其实每个点出入的所有边颜色一定不同再想一下,其实每个点出入的所有边颜色一定不同
那 么 答 案 就 是 度 数 最 大 的 那 个 点 那么答案就是度数最大的那个点那么答案就是度数最大的那个点
每 次 匹 配 也 要 先 为 度 数 大 的 进 行 匹 配 每次匹配也要先为度数大的进行匹配每次匹配也要先为度数大的进行匹配
否 则 会 导 致 几 根 可 以 用 相 同 颜 色 的 线 用 了 不 同 颜 色 否则会导致几根可以用相同颜色的线用了不同颜色否则会导致几根可以用相同颜色的线用了不同颜色

#include<bits/stdc++.h>
using namespace std;
#define pb push_back
#define endl '\n'
typedef long long ll;
const int maxn = 1e6+10;
int match[maxn];
vector<int> g[maxn];
bool vis[maxn];
bool dfs(int u) {
    for (auto v : g[u]) {
        if (!vis[v]) {
            vis[v] = 1;
            if (!match[v] || dfs(match[v])) {
                match[v] = u;
                match[u] = v;
                return 1;
            }
        }
    }
    return 0;
}
int d[maxn],u[maxn],v[maxn],id[maxn];
int col[1010][1010];
bool cmp(int x, int y) {
    return d[x] > d[y];
}
int main()
{
    ios::sync_with_stdio(false);
    cin.tie(0);cout.tie(0);
    int n, m, ans = 0;
    cin >> n >> m;
    for (int i = 1; i <= m; i++) {
        cin >> u[i] >> v[i];
        d[u[i]]++, d[v[i]]++;//点的出入度
		//最大颜色为,最大点的出入度
        ans = max(ans, max(d[u[i]], d[v[i]]));
    }
	//下标
    for (int i = 1; i <= n; i++) id[i] = i;
    for (int i = 1; i <= ans; i++) {//枚举颜色
        for (int j = 1; j <= m; j++)//枚举边
           if(!col[u[j]][v[j]]){//如果这条边没有颜色,就建边
                g[u[j]].pb(v[j]);
                g[v[j]].pb(u[j]);
            }
        for (int j = 1; j <= n; j++)
            match[j] = 0;
        sort(id + 1, id + 1 + n, cmp);//优先找最大边
		//二分图匹配
        for (int j = 1, p = id[j]; j <= n; j++, p = id[j])
            if (!match[p]){//该点没有配对
                for(int k = 1; k <= n; k++) vis[k] = 0;
                dfs(p);//配对
            }
        for (int j = 1; j <= n; j++){
            if (match[j])//j点有匹配对
		//j与匹配对点的颜色,这条边的颜色==i,j的匹配对出入度-1
        col[j][match[j]] = i, d[j]--;//match[j],遍历过程中也会--
        g[j].clear();
        }
    }
    cout << ans << endl;
    for (int i = 1; i <= m; i++)
     cout << col[u[i]][v[i]] << endl;
    return 0;
}

方法二:Vizing定理:

在图论中有一个Vizing定理:二分图边着色所需最小颜色数,等于图中点的最大度数。我们设当前需要在u和v之间加一条边,设u点尚未用过的最小的颜色编号是x,v点尚未用过的最小颜色编号是y。那么如果x=y,就直接令边(u,v)的颜色是x(y)即可;如果x<y,我们依然令(u,v)的颜色是x,然后让点v原有的颜色为x的边变色为y,接下来对于这条边连接的点v',让它原有的颜色为y的边再变色为x……就这样一直修改下去,修改路径是一条从v出发,颜色依次是x、y、x……的边构成的路径(不会经过u)。
修改这条增广路的操作可以用dfs实现,复杂度是O(n)。在添加每条边的时候都有可能需要修改,所以整体复杂度O(mn)。

#include<bits/stdc++.h>
using namespace std;
const int maxn=1001;
int es[maxn][maxn];
int color[maxn<<1];
typedef pair<int,int> pir;
map<pir,int> mp;
void dfs(int u,int v,int x,int y){
    int to=es[v][x];//小的颜色边
    //把uv边从y色染成x色
    es[u][x]=v;//两点链接,此时u点颜色是x
	es[v][x]=u;//x颜色边,链接u
    if(!to){
    //如果v之前没有x色的边
        es[v][y]=0;
    }else{//有x边,则原本x边变成y;
        dfs(v,to,y,x);
    }
}
int main(){
    int n,m,cur[2];
    cin>>n>>m;
    int ans=0;
    for(int i=1,u,v;i<=m;i++){
        cin>>u>>v;
        mp[pir(u,v)]=i;
        mp[pir(v,u)]=i;
        cur[0]=cur[1]=1;//初始下一个需要的颜色
        while(es[u][cur[0]])
            cur[0]++;//此时这个点,下一条边所需最小颜色
        while(es[v][cur[1]])
            cur[1]++;
        if(cur[0]>cur[1]){
            swap(u,v);swap(cur[0],cur[1]);
        }
        ans=max(ans,cur[1]);
        if(cur[0]==cur[1]){
            es[u][cur[0]]=v;//u,v边颜色为cur[0]
            es[v][cur[0]]=u;
        }else{//小颜色边 大颜色边 
            dfs(u,v,cur[0],cur[1]);
        }
    }
    cout<<ans<<endl;
    for(int i=1;i<=n;i++){
        for(int j=1;j<=ans;j++){
            int v=es[i][j];//这条边,是否有这个颜色
            if(v){
                color[mp[pir(i,v)]]=j;
            }
        }
    }
    for(int i=1;i<=m;i++)
        cout<<color[i]<<endl;
	return 0;
}

霍尔定理

HALL定理:对于一个二分图,如果对于左边任意子集S,其对应边连接了一个右边的点集T,都有|S|<=|T|,那么这个二分图有完美匹配(冲要)
任何一个男生,连接的女朋友数量都大于男朋友,因此完美匹配
 

#include <bits/stdc++.h>
using namespace std;
#define int long long
const int maxn = 2e5+10;
int n, s;
int a[maxn*2], b[maxn*4];
bool check(int x) {
    int mn = 0x3f3f3f3f;
    for(int i = 1; i<=n*2; i++) {
        int ll = lower_bound(b+1, b+1+n*4, a[i]-x) - b;
        int rr = upper_bound(b+1, b+1+n*4, a[i]+x) - b - 1;
        mn = min(mn, i - ll);
        if(i - rr > mn) return false;
    }
    return true;
}
signed main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    cin >> n >> s;
    for(int i = 1; i<=n; i++) cin >> a[i];
    for(int i = 1; i<=n; i++) cin >> b[i];
    sort(a+1, a+1+n);
    sort(b+1, b+1+n);
    for(int i = 1; i<=n; i++) a[i] += s;
    for(int i = n+1; i<=n*2; i++) a[i] = a[i-n] + s;
    for(int i = n+1; i<=4*n; i++) b[i] = b[i-n] + s;
    int l = 0, r = s/2;
    while(l < r) {
        int mid = l + r >> 1;
        if(check(mid)) r = mid;
        else l = mid+1;
    }
    cout << l << "\n";
    return 0;
}

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值