图论——二分图+网络流24题 洛谷题单

1.P1129 [ZJOI2007]矩阵游戏

题意:给你一个n*n的黑白方阵,每次可以对该矩阵进行两种操作:

行交换操作:选择矩阵的任意两行,交换这两行(即交换对应格子的颜色)。
列交换操作:选择矩阵的任意两列,交换这两列(即交换对应格子的颜色)。

现在问你最少通过几次操作,使得方阵的主对角线(左上角到右下角的连线)上的格子均为黑色。

做法:

我们最终要达到主对角线上的格子均为黑色,也就是aii = 1 (1<=i<=n) ,即每一行每一列都可以匹配,
而我们的操作可以使得任意两行或者任意两列交换,并且行的交换和列的交换不影响匹配的个数,
也就是我们只需要让初始情况下n行n列之间产生n个匹配(第i行可以与第j列匹配到)
然后将行和列作为点建立二分图,如果aij = 1表示行列点之间有边,求二分图最大匹配,如果是n就输出YES,否则输出NO
我这里用的是网络流求二分图最大匹配(设每条边的容量为1,源点向每个行节点连边,每个列节点向汇点连边),跑最大流即可求出二分图最大匹配

点击查看代码块
#include <bits/stdc++.h>
using namespace std;
const int maxn=4e4+10;
const int inf=0x3f3f3f3f;
int n,T;
struct edge{
    int v,next;
    int c;
}e[maxn<<4];
int head[maxn<<1],cnt=0;

void add(int u,int v,int c){
    e[cnt].v=v;
    e[cnt].c=c;
    e[cnt].next=head[u];
    head[u]=cnt++;
}

int dis[maxn<<1];

bool bfs(int s,int t){//分层
    memset(dis,-1,sizeof(dis));
    dis[s] = 0;
    queue<int> q;
    q.push(s);
    while(!q.empty()){
        int u=q.front();
        q.pop();
        for (int i=head[u];~i;i=e[i].next){
            int v=e[i].v;
            int c=e[i].c;
            if(c>0 && dis[v] == -1){
                dis[v] = dis[u] + 1;
                q.push(v);
            }
        }
    }
    return dis[t]!=-1;
}

int dfs(int u,int t,int flow){
    if(u == t){
        return flow;
    }
    int delta = flow;
    for (int i=head[u];~i;i=e[i].next){
        int v=e[i].v,c=e[i].c;
        if(c>0 && dis[v] == dis[u]+1){
            int d=dfs(v,t,min(delta,c));
            if(d == 0) dis[v] = 0;
            e[i].c-=d;
            e[i^1].c+=d;
            delta -= d;
            if(delta == 0) break;
        }
    }
    return flow-delta;
}

int dinic(int s,int t){
    int maxflow = 0;
    while(bfs(s,t)){
        maxflow += dfs(s,t,inf);
    }
    return maxflow;
}

int main(){
    cin>>T;
    while(T--){
        memset(head,-1,sizeof(head));
        cnt=0;
        scanf("%d",&n);
        for (int i=1;i<=n;i++){
            for (int j=1;j<=n;j++){
                int x;
                scanf("%d",&x);
                if(x == 1) add(i,j+n,1),add(j+n,i,0);
            }
        }
        for (int i=1;i<=n;i++){
            add(0,i,1),add(i,0,0);
            add(i+n,2*n+1,1),add(2*n+1,i+n,0);
        }
        int ans = dinic(0,2*n+1);
        if(ans == n) printf("Yes\n");
        else printf("No\n");
    }
    return 0;
}

2.网络流24题 P2756 飞行员配对方案问题


做法:
很明显,建立二分图,外籍飞行员和英国飞行员之间连边,求一个最大匹配即可。路径的记录匈牙利算法直接可以得到,
如果用最大流来做可以遍历每条边,如果当前边容量为0,表示有一个匹配,输出边的两个端点

点击查看代码块
#include <bits/stdc++.h>
using namespace std;
const int maxn = 300;
typedef long long ll;
const int inf=0x3f3f3f3f;
int n,m;
int s=0,t;//超级源点是0,超级汇点t是n+1
int head[maxn],cnt=0;
int match[maxn];//记录匹配
int dis[maxn];//分层图
bool vis[maxn];

struct edge{
    int v,next;
    int c;
}e[maxn<<3];
void add(int u,int v,int c){
    e[cnt].v=v;
    e[cnt].c=c;
    e[cnt].next=head[u];
    head[u]=cnt++;
}

void create_Graph(){
    cnt=0;
    for (int i=0;i<maxn;i++) head[i]=-1;
    cin>>m>>n;
    t=n+1;
    int u,v;
    cin>>u>>v;
    vector<int>G[2];
    int flag[maxn];
    for (int i=1;i<=n;i++) flag[i]=0;
    while(u!=-1 && v!=-1){
        if(u<=m && v<=n){//u和v之间建边
            add(u,v,1);add(v,u,0);
            if(flag[u] == 0){
                flag[u] = 1;
                G[0].push_back(u);
            }
            if(flag[v] == 0){
                flag[v] = 1;
                G[1].push_back(v);
            }
        }
        scanf("%d%d",&u,&v);
    }
    for (int i=1;i<=m;i++){
        add(s,i,1);add(i,s,0);
    }
    for (int i=m+1;i<=n;i++){
        add(i,t,1);add(t,i,0);
    }
}

bool bfs(int s,int t){
    for (int i=0;i<=t;i++) dis[i]=-1;
    dis[s]=0;
    queue<int> q;
    q.push(s);
    while(!q.empty()){
        int u=q.front();q.pop();
        for (int i=head[u];~i;i=e[i].next){
            int v=e[i].v;
            int c=e[i].c;
            if(c>0 && dis[v]==-1){
                dis[v]=dis[u]+1;
                q.push(v);
            }
        }
    }
    return dis[t]!=-1;
}

int dfs(int u,int flow){
    if(u == t) return flow;
    int delta = flow;
    for (int i=head[u];~i;i=e[i].next){
        int v=e[i].v;
        int c=e[i].c;
        if(c>0 && dis[v]==dis[u]+1){
            int d=dfs(v,min(delta,c));
            if(d==0) dis[v]=0;
            e[i].c-=d;
            e[i^1].c+=d;
            delta-=d;
            if(delta == 0) break;
        }
    }
    return flow-delta;
}

int dinic(int s,int t){
    int maxflow = 0;
    while(bfs(s,t)){
        maxflow += dfs(s,inf);
    }
    return maxflow;
}

int main(){
    create_Graph();
    int maxflow = dinic(s,t);
    printf("%d\n",maxflow);
    for (int i=0;i<cnt;i+=2){
        if(e[i].v!=s && e[i^1].v!=s && e[i].v!=t && e[i^1].v!=t){
            if(e[i].c == 0 ){//残量网络中正向边为0表示有流经过这条边
                printf("%d %d\n",e[i^1].v,e[i].v);
            }
        }
    }
    return 0;
}

3.网络流24题 P2764 最小路径覆盖问题

做法:

模板题,最小路径覆盖=原图点数-新图最大匹配
将原图中的边u-v做处理:u点拆成u和u'两点,v拆成v和v'两点,然后u向v'连边
证明:
原图中每个点初始我们可以看为一个连通块,则有n个连通块,现在如果新图中存在一个匹配u-v',则说明原图中u-v有一条边,连通块的个数减一,一条路径上覆盖的点数+1
于是最小路径覆盖就是原图点数-新图的最大匹配

点击查看代码块
#include <bits/stdc++.h>
using namespace std;
const int maxn=1010;
const int maxm=1e5+10;
const int inf=0x3f3f3f3f;
struct edge{
	int v,next;
	int c;
}e[maxm<<2];
int head[maxn],cnt=0;
void add(int u,int v,int c){
	e[cnt].v=v;
	e[cnt].c=c;
	e[cnt].next=head[u];
	head[u]=cnt++;
}

int n,m;
int dis[maxn];
int s=0,t=1000;
int fa[maxn];

bool bfs(int s,int t){
	memset(dis,-1,sizeof(dis));
	dis[s]=0;
	queue<int>q;
	q.push(s);
	while(!q.empty()){
		int u=q.front();
		q.pop();
		for (int i=head[u];~i;i=e[i].next){
			int v=e[i].v,c=e[i].c;
			if(c>0 && dis[v]==-1){
				dis[v]=dis[u]+1;
				q.push(v);
			}
		}
	}
	return dis[t]!=-1;
}

int dfs(int u,int t,int flow){
	if(u==t) return flow;
	int delta=flow;
	for (int i=head[u];~i;i=e[i].next){
		int v=e[i].v;
		int c=e[i].c;
		if(c>0 && dis[v]==dis[u]+1){
			int d=dfs(v,t,min(delta,c));
			if(!d) dis[v]=0;
			e[i].c-=d;
			e[i^1].c+=d;
			delta-=d;
			if(delta == 0) break; 
		}
	}
	return flow-delta;
}

int dinic(int s,int t){
	int maxflow = 0;
	while(bfs(s,t)){
		maxflow+=dfs(s,t,inf);
	}
	return maxflow;
}

void slv(int x){
	printf("%d ",x);
	for (int i=head[x];~i;i=e[i].next){
		int v=e[i].v;
		if(e[i].c == 0 && v>n && fa[v-n] == v-n){
			fa[v-n] = x;
			slv(v-n);
		}
	}
}

int main(){
	memset(head,-1,sizeof(head));
	cnt=0;
	cin>>n>>m;
	for (int i=1;i<=n;i++) fa[i] = i;
	for (int i=1;i<=m;i++){
		int u,v;
		scanf("%d%d",&u,&v);
		add(u,v+n,1),add(v+n,u,0);
	}
	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);
	}
	int ans = dinic(s,t);
	for (int u=1;u<=n;u++){
		if(fa[u] == u){
			slv(u);
			printf("\n");
		}
	}
	printf("%d\n",n-ans);
	return 0;
}

4.P6062 [USACO05JAN]Muddy Fields G

做法:

此题的建图很巧妙,我们开始可以先考虑类似于贪心的策略。
假如我们都在行上加木板,则我们所需要的最少的木板数就是每行所有连通块的数量之和,列也一样。
这样,对于每个泥地,我们要么选择的是横向的最长连通块,要么是纵向的最长连通块,也就是每个泥地都至少要被自己所在的行连通块或列连通块的其中一个覆盖
然后我们可以把泥地作为一条边,边连接的是泥地所对应的行连通块和纵连通块
再求一个二分图的最小点覆盖即可。
由 Konig 定理,只需求出该二分图的最大匹配即可。
网络流建图跑一边dinic即可

点击查看代码块
/*
基于二分图
最小点覆盖 = 二分图最大匹配
*/
#include <bits/stdc++.h>
using namespace std;
const int inf=0x3f3f3f3f;
const int maxn=1010;
int head[maxn],cnt=0;
struct edge{
	int v,next;
	int c;
}e[maxn<<2];
void add(int u,int v,int c){
	e[cnt].v=v;
	e[cnt].c=c;
	e[cnt].next=head[u];
	head[u]=cnt++;
}
char g[maxn][maxn];
int n,m;
int dis[maxn];
int s=0,t=1000;
int g1[maxn][maxn],g2[maxn][maxn];

bool bfs(int s,int t){
	memset(dis,-1,sizeof(dis));
	dis[s] = 0;
	queue<int> q;
	q.push(s);
	while(!q.empty()){
		int u=q.front();
		q.pop();
		for (int i=head[u];~i;i=e[i].next){
			int v=e[i].v,c=e[i].c;
			if(c>0 && dis[v]==-1){
				dis[v]=dis[u]+1;
				q.push(v);
			}
		}
	}
	return dis[t]!=-1;
}

int dfs(int u,int t,int flow){
	if(u==t) return flow;
	int delta = flow;
	for (int i=head[u];~i;i=e[i].next){
		int v=e[i].v;
		int c=e[i].c;
		if(c>0 && dis[v]==dis[u]+1){
			int d=dfs(v,t,min(delta,c));
			if(!d) dis[v]=0;
			e[i].c-=d;
			e[i^1].c+=d;
			delta-=d;
			if(delta == 0) break;
		}
	}
	return flow-delta;
}

int dinic(int s,int t){
	int maxflow = 0;
	while(bfs(s,t)){
		maxflow+=dfs(s,t,inf);
	}
	return maxflow;
}

void clear(){
	memset(e,0,sizeof(e));
	memset(head,-1,sizeof(head));
	cnt=0;
}

int main(){
	clear();
	cin>>n>>m;
	for (int i=1;i<=n;i++){
		getchar();
		for (int j=1;j<=m;j++){
			scanf("%c",&g[i][j]);
		}
	}
    int tot1 = 0,tot2 = 0;
    for (int i=1;i<=n;i++){
        int flag = 0;
        for (int j=1;j<=m;j++){
            if(g[i][j] == '*' && flag == 0){
                g1[i][j] = ++tot1;flag = 1;
            }
            else if(g[i][j]=='*' && flag == 1){
                g1[i][j]=tot1;
            }
            else if(g[i][j] == '.') flag=0;
        }
    }

    for (int j=1;j<=m;j++){
        int flag = 0;
        for (int i=1;i<=n;i++){
            if(g[i][j]=='*' && flag == 0){
                g2[i][j]=++tot2;flag=1;
            } 
            else if(g[i][j]=='*' && flag == 1){
                g2[i][j]=tot2;
            }
            else if(g[i][j]=='.') flag = 0;
        }
    }
    // cout<<"tot1 = "<<tot1<<" tot2 = "<<tot2<<endl;
    for (int i=1;i<=n;i++){
        for (int j=1;j<=m;j++){
            if(g[i][j]=='*'){
                int u = g1[i][j],v=g2[i][j];
                add(u,tot1+v,1);add(tot1+v,u,0);
            }   
        }
    }
    for (int i=1;i<=tot1;i++){
        add(s,i,1);add(i,s,0);
    }
    for (int i=1;i<=tot2;i++){
        add(tot1+i,t,1);add(t,tot1+i,0);
    }
    int ans = dinic(s,t);
    printf("%d\n",ans);
    return 0;
}

持续更新中。。。

猜你喜欢

转载自www.cnblogs.com/wsl-lld/p/13399653.html