各种与二分图相关的定理
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定理
- 无向图G的最大团等于其补图\(G'\)的最大独立集
补图转化思想可能成为某些题目的突破口。
- 设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最小路径点覆盖。