Loading

各种与二分图相关的定理

1二分图最小点覆盖

顾名思义,就是给定一个最小的点集\(S\),使得图中任意一条边都有至少一个端点属于\(S\),这个问题被称为二分图的最小点覆盖,简称最小覆盖。

1.1定理

二分图最小点覆盖包含的点数等于二分图最大匹配包含的边数。

1.2 构造方法

在二分图最大匹配的基础上,从左边的非匹配点出发,执行一次寻找增广路的过程(一定会失败),标记其中经过的点。

取左边未标记的点,右边标记的点,就是二分图最小点覆盖。

经过上述构造方法后,左边非匹配点一定被标记,因为是出发点,右边匹配点一定没有被标记,否则就找到了增广路。一对匹配点要么都被标记,要么都没被标记。

我们上面取得点都是匹配点,所以我们取的点恰好每一个点都覆盖了一条边。

而因为所以的边至少有一个端点是匹配点,否则就产生了增广路,所以所有我们取得点构成了二分图最小点覆盖。

故定理1.1成立,构造方法成立。

证毕。

1.3例题

#include<iostream>
#include<cstdio>
#include<cmath>
#include<algorithm>
#include<cstring>
#include<sstream>
#include<queue>
#include<map>
#include<vector>
#include<set>
#include<deque>
#include<cstdlib>
#include<ctime>
#define dd double
#define ld long double
#define ll long long
#define ull unsigned long long
#define N 300
#define M 3000
using namespace std;

const int INF=0x3f3f3f3f;

inline ll read(){
	ll x=0,f=1;
	char ch=getchar();
	while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
	while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
	return x*f;
}

struct edge{
	int to,next;
	inline void intt(int to_,int ne_){
		to=to_;next=ne_;
	}
};
edge li[M];
int head[N],tail;

inline void add(int from,int to){
	li[++tail].intt(to,head[from]);
	head[from]=tail;
}

int n,m,k,ans;

int match[N];
bool op,vis[N];
inline bool dfs(int k){
	for(int x=head[k];x;x=li[x].next){
		int to=li[x].to;
		if(!vis[to]){
			vis[to]=1;
			if(!match[to]||dfs(match[to])){
				match[to]=k;
				return 1;
			}
		}
	}
	return 0;
}

int main(){
	while(1){
		n=read();
		if(n==0) break;
		memset(match,0,sizeof(match));
		ans=0;memset(head,0,sizeof(head));
		memset(li,0,sizeof(li));tail=0;
		m=read();k=read();
		for(int i=1;i<=k;i++){
			int id=read(),from=read(),to=read();
			from++;to++;
			if(from==1||to==1) continue;
			add(from,to+n);
		}
		for(int i=1;i<=n;i++){
			memset(vis,0,sizeof(vis));
			if(dfs(i)) ans++;
		}
		printf("%d\n",ans);
	}
	return 0;
}

2 二分图最大独立集

给定一张无向图,图的最大独立集是一个最大的点集,要求任意两点之间没有边相连。

2.1定理

  1. 无向图G的最大团等于其补图\(G'\)的最大独立集

补图转化思想可能成为某些题目的突破口。

  1. 设G是有n个节点的二分图,G的最大独立集的大小等于n减去最大匹配数。

证明:因为选出最大的点集构成独立集,等价于去掉最少的点及其连边,使得剩下的点之间没有边,等价于点数减去最大匹配数。

2.2例题

建图比较巧妙,一行黑格,一行白格,两种颜色的格子分别为左边的点和右边的点,以能够攻击在点之间连边,即可把该问题转化为最大独立集问题。

#include<iostream>
#include<cstdio>
#include<cmath>
#include<algorithm>
#include<cstring>
#include<sstream>
#include<queue>
#include<map>
#include<vector>
#include<set>
#include<deque>
#include<cstdlib>
#include<ctime>
#define dd double
#define ld long double
#define ll long long
#define ull unsigned long long
#define N 201
#define M 50000
using namespace std;

const int INF=0x3f3f3f3f;
const int fx[]={0,-3,-3,+1,-1,+3,+3,-1,+1};
const int fy[]={0,-1,+1,+3,+3,+1,-1,-3,-3};

inline int read(){
	int x=0,f=1;
	char ch=getchar();
	while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
	while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
	return x*f;
}

struct edge{
    int to,next;
    inline void intt(int to_,int ne_){
        to=to_;next=ne_;
    }
};
edge li[M*10];
int head[M],tail;

inline void add(int from,int to){
    li[++tail].intt(to,head[from]);
    head[from]=tail;
}

int n,m,nb,nw,k,ans,cnt;
bool off[N][N];

int match[M];
bool vis[M];

inline bool dfs(int k){
    for(int x=head[k];x;x=li[x].next){
        int to=li[x].to;
        if(!vis[to]){
            vis[to]=1;
            if(!match[to]||dfs(match[to])){
                match[to]=k;
                return 1;
            }
        }
    }
    return 0;
}

int main(){
    n=read();m=read();k=read();
    nw=(n+1)/2*m;
    nb=nw;
    for(int i=1;i<=k;i++){
        int x=read(),y=read();
        if(off[x][y]) cnt++;
        off[x][y]=1;
    }
    for(int i=1;i<=n;i+=2){
        for(int j=1;j<=m;j++){// white (i,j)
            if(off[i][j]) continue;
            for(int k=1;k<=8;k++){
                int dx=i+fx[k],dy=j+fy[k];
                if(dx<=0||dy<=0||dx>n||dy>m||off[dx][dy]) continue;
                int from=(i/2)*m+j,to=(dx/2-1)*m+dy+nw;
                add(from,to);
//                printf("from:%d to:%d\n",from,to);
            }
        }
    }
//    printf("tail:%d\n",tail);
    for(int i=1;i<=nw;i++){
//        if(off[(i/m)*2+1][i%m]){
//            printf("canwork\n");
//            continue;
//        }
        memset(vis,0,sizeof(vis));
        if(dfs(i)) ans++;
    }
//    printf("%d\n",ans);
    printf("%d\n",n*m-ans-k+cnt);
    return 0;
}
/*
8 7 5
1 1 
5 4
2 3
4 7
8 3
*/

3DAG最小路径点覆盖

给定一张有向无环图,要求用尽量少的不想交的简单路径,覆盖有向无环图的所有顶点,找个问题被称为有向无环图的最小路径点覆盖问题。

简称最小路径覆盖。

3.1求解

把原无向图\(G\)中的每一个节点\(a\)拆成\(a,a+n\),对于每条边\((x,y)\),从\(x\)\(y+n\)连一条边,由此,我们建成了一张拆点二分图,记为\(G_2\)

  • 定理:DAG G的最小路径点覆盖包含的路径条数,等于n减去拆点二分图\(G_2\)的最大匹配数。

证明:对于DAG G的最小路径覆盖的路径的非终点,其与\(G_2\)中左边的匹配点是一一对应的,对于DAG G的最小路径覆盖的路径的终点,其与\(G_2\)中左边的非匹配点是一一对应的。

由此,求最小路径覆盖等价于让终点最少,等价于让匹配点更多。证毕。

3.2构造

如果用匈牙利算法,只需再记录每个左边的点匹配的右边的点是哪一个即可,如果用网络流求解,可以检查每条边的流量来判断与之匹配的点是哪一个。

3.3例题

给出网络流的代码

让用网络流做就没敢写匈牙利

#include<iostream>
#include<cstdio>
#include<cmath>
#include<algorithm>
#include<cstring>
#include<sstream>
#include<queue>
#include<map>
#include<vector>
#include<set>
#include<deque>
#include<cstdlib>
#include<ctime>
#define dd double
#define ld long double
#define ll long long
#define ull unsigned long long
#define N 5000
#define M 100000
using namespace std;

const int INF=0x3f3f3f3f;

inline int Min(int a,int b){
    return a>b?b:a;
}

inline int read(){
	int x=0,f=1;
	char ch=getchar();
	while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
	while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
	return x*f;
}

struct edge{
    int to,next,f;
    inline void intt(int to_,int ne_,int f_){
        to=to_;next=ne_;f=f_;
    }
};
edge li[M];
int head[N],tail=1,now[N];

inline void add(int from,int to,int f){
    li[++tail].intt(to,head[from],f);
    head[from]=tail;
}

int n,m,s,t,ans;

queue<int> q;
int d[N];

inline bool bfs(int s){
    memset(d,0,sizeof(d));
    while(q.size()) q.pop();
    q.push(s);d[s]=1;now[s]=head[s];
    while(q.size()){
        int top=q.front();q.pop();
        for(int x=head[top];x;x=li[x].next){
            int to=li[x].to,f=li[x].f;
            if(!f||d[to]) continue;
            now[to]=head[to];d[to]=d[top]+1;
            q.push(to);
            if(to==t) return 1;
        }
    }
    if(!d[t]) return 0;
    return 1;
}

inline int dicnic(int k,int flow){
    if(k==t) return flow;
    int x,rest=flow;
    for(x=now[k];x&&rest;x=li[x].next){
        int to=li[x].to,re=li[x].f;
        if(!re||d[to]!=d[k]+1) continue;
        int val=dicnic(to,Min(rest,re));
        if(!val) d[to]=0;
        li[x].f-=val;
        li[x^1].f+=val;
        rest-=val;
    }
    now[k]=x;
    return flow-rest;
}

bool vis[N];

inline void print(int k){
    if(vis[k]) return;
    printf("%d ",k);vis[k]=1;
    for(int x=head[k];x;x=li[x].next){
        int to=li[x].to,f=li[x].f;
        if(!f&&to!=s) print(to-n);
    }
}

int main(){
//    freopen("P2764_2.in","r",stdin);
//    freopen("my.out","w",stdout);
    n=read();m=read();
    s=2*n+1;t=2*n+2;
    for(int i=1;i<=n;i++){
        add(s,i,1);add(i,s,0);
        add(i+n,t,1);add(t,i+n,0);
    }
    for(int i=1;i<=m;i++){
        int from=read(),to=read();
        add(from,to+n,1);add(to+n,from,0);
    }
    //build over
    int flow=0;
    while(bfs(s)) while(flow=dicnic(s,INF)) ans+=flow;
    //dicnic over
//    printf("Begin to print\n");
    for(int i=2;i<=4*n+1;i+=4){
        int f=li[i].f,to=li[i].to;
        if(!f&&!vis[to]){
            print(to);
            printf("\n");
        }
    }
//    printf("ans:%d\n",ans);
    printf("%d",n-ans);
    return 0;
}

4DAG最小路径可重复点覆盖

先对有向图进行传递闭包,然后再跑DAG最小路径点覆盖。

posted @ 2021-04-12 10:58  hyl天梦  阅读(153)  评论(0编辑  收藏  举报