匹配问题总结

**~~

匹配问题总结

**~~

二分图

性质:1.无奇圈
2.对于网格图,以每个格子为点,图为二分图。
染色法判图是否为二分图

int color[MAX_N];
bool dfs(int u,int c)
{
	color[u]=c;
	reps(u){
		if(color[v]){
			if(color[v]==c)	return false;
			continue;
		}
		if(!dfs(v,-c))	return false; 
	}
	return true;
}
bool Bipartite_Graph_Check(int u)
{
	ms(color);
	if(dfs(u,1))	return true;
	else return false;
}

二分图匹配
最大匹配:在二分图中,最多的没有公共顶点的边的数量。
最小点覆盖:在二分图中,所有边至少有一个顶点在一个点集S中,这样的点集S的最少元素个数。(最小点覆盖=最大匹配)
最大独立点集:在二分图中,选择一些点,使得这些点两两没有边直接相连。(最大独立点集=总点数-最大匹配)
DAG转化成二分图:将一个点拆分成两个点:入点和出点,连一条边(如A→B),就把A的出点和B的入点相连。
DAG的链:一条从A走到B的路径上的所有点。
最小不相交路径覆盖:在DAG中,用最少的不相交的链覆盖整个DAG上的点(转化成二分图,则最小路径覆盖=点数(原点数,非拆点后点数)-二分图的最大匹配)
最小可相交路径覆盖:先用floyd求传递闭包(两点间的可达性),利用求出的新图按最小不相交路径覆盖处理。

模板
二分匹配(Hungary O ( V E ) O(VE) O(VE))

struct Edge{ int to,nxt; };
struct Bipartite_matching{
	int nl,nr;//nl左侧点数,nr右侧点数,1 - nl左侧点nl+1 - nr右侧点
	Edge e[MAX_E<<1];
	int head[MAX_N],tote;
	int match[MAX_N];
	bool used[MAX_N];
	void init(int l,int r)
	{
		nl=l,nr=r,tote=0;
		repi(i,1,nl+nr)	head[i]=match[i]=0;
	}
	void add_edge(int u,int v)
	{
		e[++tote].to=v,e[tote].nxt=head[u],head[u]=tote;
		e[++tote].to=u,e[tote].nxt=head[v],head[v]=tote;
	}
	bool dfs(int u)
	{
		used[u]=true;
		reps(u){
			int w=match[v];
			if(!w||!used[w]&&dfs(w)){
				match[u]=v,match[v]=u;
				return true;
			}
		}
		return false;
	}
	int cal()//判是否是完美匹配时若一个点没匹配到可直接返回0不用求完
	{
		int res=0;
		repi(i,1,nl)if(!match[i]){
			repi(j,1,nl+nr) used[j]=false;
			if(dfs(i))	res++;
		}
		return res;
	}
}bm;

二分匹配( Hopcroft_Carp O ( s q r t ( v ) E ) O(sqrt(v)E) O(sqrt(v)E) )

const int inf=0x3f3f3f3f;
struct Edge{ int to,nxt; };
struct Hopcroft_Carp{
	int um[MAX_N],vm[MAX_N];
	int dx[MAX_N],dy[MAX_N],dis;
	bool vis[MAX_N];
	Edge e[MAX_E];
	int head[MAX_N],tote;
	void init(int n){ repi(i,1,n) head[i]=0; tote=0; }//n为左边点数 
	void add_edge(int u,int v){ e[++tote].to=v,e[tote].nxt=head[u],head[u]=tote; }//左边点连右边点,左右边点均为下标1开始
	bool SearchP(int n)
	{
	    queue<int>q;
	    dis=inf;
	    memset(dx,-1,sizeof(dx)),memset(dy,-1,sizeof(dy));
	    repi(i,1,n)if(um[i]==-1) q.push(i),dx[i]=0;    
	    while(!q.empty()){
	        int u=q.front(); q.pop();
	        if(dx[u]>dis) break;
	        reps(u)if(dy[v]==-1){
                dy[v]=dx[u]+1;
                if(vm[v]==-1) dis=dy[v];
                else dx[vm[v]]=dy[v]+1,q.push(vm[v]);
	        }
	    }
	    return (dis!=inf);
	}
	bool dfs(int u)
	{
		reps(u)if(!vis[v]&&dy[v]==dx[u]+1){
            vis[v]=1;
            if(vm[v]!=-1&&dy[v]==dis) continue;
            if(vm[v]==-1||dfs(vm[v])){
                vm[v]=u,um[u]=v;
                return true;
            }
	    }
	    return false;
	}
	int cal(int n)//n为左边点数
	{
	    int res=0;
	    memset(um,-1,sizeof(um)),memset(vm,-1,sizeof(vm));
	    while(SearchP(n)){
	        ms(vis);
	        repi(i,1,n)	if(um[i]==-1&&dfs(i)) ++res;
	    }
	    return res;
	}
}hk;

二分图多重匹配
左侧点匹配右侧点,但右侧点可被匹配多次
模板

/*
左侧只匹配一次,右侧匹配多次
若左侧多次不可左侧拆点后使用(不能限制拆除的点连的右侧点都不同),此时要用网络流
*/
struct Edge{int to,nxt;};
struct Bipartite_multi_matching{
	int ul,ur;//左侧点数 右侧点数 下标均1开始
	Edge e[MAX_E];
	int head[MAX_N],tote;
	void add_edge(int u,int v){e[++tote].to=v,e[tote].nxt=head[u];head[u]=tote;}
	int num[MAX_N],match[MAX_N][MAX_N];//num 右侧每个点允许匹配数量,match[i][0]右侧每个点最终匹配数量,1-match[i][0]记录分别匹配了哪个点
	bool used[MAX_N];
	void init(int l,int r)
	{
		ul=l,ur=r;
		repi(i,1,ul) head[i]=0;
	}
	bool dfs(int u)
	{
		reps(u)if(!used[v]){
			used[v]=true;	
			if(match[v][0]<num[v]){
				match[v][++match[v][0]]=u;
				return true;
			}
			repi(i,1,match[v][0]){
				if(dfs(match[v][i])){
					match[v][i]=u;
					return true;
				}
			}
		}
		return false;
	}
	int cal()
	{
		int res=0;
		repi(i,1,ur)	match[i][0]=0;
		repi(i,1,ul){
			repi(i,1,ur) used[i]=false;
			if(dfs(i))	res++;
		}
		return res;
	}
}bmm;

二分图最大权匹配
每条匹配边有权值,找权值和最大的匹配
O ( n 3 ) O(n^3) O(n3)

const ll inf=4e18;
struct Kuhn_Munkres{//最小权匹配则均加负权边(inf也是)
    int n;//每侧点数(左右要相等)若左右不相等,少的一边加虚节点(n取左右两侧点最大值),连其它点都可以边权为0(直接初始化边权0)
    ll g[MAX_N][MAX_N];//边权
    ll lx[MAX_N],ly[MAX_N],slack[MAX_N];
    int nxtx[MAX_N],nxty[MAX_N],visx[MAX_N],visy[MAX_N],pre[MAX_N],q[MAX_N],ql,qr;
    void init(int _n)
    {
    	n=_n;
    	repi(i,1,n)repi(j,1,n) g[i][j]=-inf;//不是要找完美匹配的话,设0,相应的注意nxtx,nxty时要判断是否有边
	}
	void add_edge(int u,int v,ll w){ g[u][v]=w; }//u左边点,v右边点,两侧点编号均为1-n
    bool check(int now)
	{
        visy[now]=1;
        if(nxty[now]!=-1) {
            if(!visx[nxty[now]]) q[++qr]=nxty[now],visx[nxty[now]]=1;
            return false;
        }
        while(now!=-1) swap(now,nxtx[nxty[now]=pre[now]]);
        return true; 
    }
    void bfs(int s)
	{
        repi(i,1,n) slack[i]=inf,visx[i]=visy[i]=false;
        ms(visx),ms(visy);
        q[ql=qr=1]=s;
        visx[s]=qr=1;
        ll d;
        while(1){
            while(ql<=qr){
                int u=q[ql++];
                repi(i,1,n)if(!visy[i]){
                    ll tmp=lx[u]+ly[i]-g[u][i];
                    if(slack[i]>=tmp){
                        slack[i]=tmp,pre[i]=u;
                        if(!slack[i]&&check(i)) return;
                    }
                }
            }
            ll d=inf;
            repi(i,1,n)if(!visy[i]&&slack[i]) d=min(d,slack[i]);
            repi(i,1,n){
                if(visx[i]) lx[i]-=d;
                if(visy[i]) ly[i]+=d;
                else slack[i]-=d;
            }
            ql=1,qr=0;
            repi(i,1,n) if(!visy[i]&&!slack[i]&&check(i)) return;
        }
    }
    ll cal()
	{
		repi(i,1,n) nxtx[i]=nxty[i]=-1,ly[i]=0;
        repi(i,1,n) lx[i]=*max_element(g[i]+1,g[i]+1+n);
        repi(i,1,n) bfs(i);
        ll res=0;
        repi(i,1,n) if(nxtx[i]!=-1) res+=g[i][nxtx[i]];//nxtx记录左边i连接右边哪个点,nxty记录右边i连左边哪个点
        return res;
    }
}km;

一般图

一般图匹配带花树算法,处理一般图的最大匹配
O ( n 3 ) O(n^3) O(n3)

int n,m;//点数 边数
bool G[MAX_N][MAX_N];
int Match[MAX_N],Count;//记录每个点与哪个点匹配,0为无匹配 count匹配数(匹配对数=匹配数/2)
bool InQueue[MAX_N],InPath[MAX_N],InBlossom[MAX_N];
int Head,Tail,Queue[MAX_N];
int Start,Finish,NewBase;
int Father[MAX_N],Base[MAX_N];
void CreateGraph()//建图
{
	si(n),si(m);
    repi(i,1,n)repi(j,1,n) G[i][j]=false;
	repi(i,1,m){
		int u,v; si(u),si(v);
		G[u][v]=G[v][u]=true;
	}
}
inline void Push(int u){ Queue[Tail++]=u,InQueue[u]=true; }
inline int Pop(){ return Queue[Head++]; }
int FindCommonAncestor(int u,int v)
{
	repi(i,1,n) InPath[i]=false;
    while(true)
	{
        u=Base[u],InPath[u]=true;
        if(u==Start) break;
        u=Father[Match[u]];
    }
    while(true)
	{
        v=Base[v];
        if(InPath[v])break;
        v=Father[Match[v]];
    }
    return v;
}
void ResetTrace(int u)
{
    int v;
    while(Base[u]!=NewBase)
	{
        v=Match[u];
        InBlossom[Base[u]]=InBlossom[Base[v]]=true;
        u=Father[v];
        if(Base[u]!=NewBase) Father[u]=v;
    }
}
void BloosomContract(int u,int v)
{
    NewBase=FindCommonAncestor(u,v);
    ms(InBlossom);
    ResetTrace(u);
    ResetTrace(v);
    if(Base[u]!=NewBase) Father[u]=v;
    if(Base[v]!=NewBase) Father[v]=u;
    repi(tu,1,n)if(InBlossom[Base[tu]]){
        Base[tu]=NewBase;
        if(!InQueue[tu]) Push(tu);
    }
}
void FindAugmentingPath()
{
	repi(i,1,n) InQueue[i]=Father[i]=0;
    repi(i,1,n)	Base[i]=i;
    Head=Tail=1;
    Push(Start),Finish=0;
    while(Head<Tail)
    {
        int u=Pop();
        repi(v,1,n)if(G[u][v]&&(Base[u]!=Base[v])&&(Match[u]!=v)){
            if((v==Start)||((Match[v]>0)&&Father[Match[v]]>0)) BloosomContract(u,v);
            else if(Father[v]==0){
                Father[v]=u;
                if(Match[v]>0) Push(Match[v]);
                else{ Finish=v;return; }
            }
        }
    }
}
void AugmentPath()
{
    int u,v,w;
    u=Finish;
    while(u>0)
    {
        v=Father[u],w=Match[v];
        Match[v]=u,Match[u]=v;
        u=w;
    }
}
void Edmonds()
{
    repi(i,1,n) Match[i]=0;
    repi(u,1,n)if(Match[u]==0){
        Start=u;
        FindAugmentingPath();
        if(Finish>0) AugmentPath();
    }
}
void PrintMatch()
{
 	Count=0;
 	repi(u,1,n)	if(Match[u]>0)	Count++;
 	printf("%d\n",Count);
 	repi(u,1,n) printf("%d%c",Match[u],ce(u,n));
}
/*
    CreateGraph();//建图 
    Edmonds();//进行匹配 
    PrintMatch();//输出匹配数和匹配 
*/

你可能感兴趣的:(图论)