图算法——二部图&网络流

本文介绍了图论中的最大二分匹配问题,包括匈牙利算法的实现细节,并提供了多个相关例题,如ZoJ1137、ZoJ1002、ZoJ1654和ZoJ1516。同时,还讨论了网络流算法中的Ford-Fulkerson算法,用于寻找图的最大流。这些算法在解决资源分配、路径规划等问题中有着广泛应用。
摘要由CSDN通过智能技术生成

二部图算法

最大二分匹配

匈牙利算法 

算法思路:

代码(template): 

#include<iostream>
#include<vector>
#include<bits/stdc++.h>
using namespace std;

#define maxn 105

int G[maxn][maxn];//存储图
bool vis[maxn];//访问数组
int match[maxn];//匹配数组
vector<int> V[maxn];//存储每个点的相邻节点

bool dfs(int start) {//深度优先搜索寻找匹配点
	for (int i = 0; i<int(V[start].size()); i++) {//遍历每一个与start相邻的节点
		int v = V[start][i];//存储与start相邻的节点
		if (!vis[v]) {//假如没有被访问过,才可进一步寻求匹配
			vis[v] = 1;//标记访问
			int tmp = match[v];//存储目前v的匹配点
			match[v] = start;//给v新的匹配点start
			if (tmp == -1 || dfs(tmp)) {//如果v没有匹配或原来v的匹配点能够匹配到其他节点
				return true;//说明v可以匹配start
			}
			match[v] = tmp;//如果不能匹配start,则回退,让v的匹配恢复原来
		}
	}
	return false;//从start节点找不到匹配顶点
}

int main() {
	int n1, n2;
	while (cin >> n1 >> n2 && n1 != 0 && n2 != 0) {
		//建图
		memset(G, -1, sizeof(G));
		int m;
		cin >> m;
		while (m--) {
			int a, b;
			cin >> a >> b;
			G[a][b] = 0;
		}
		int num = 0;
		for (int i = 1; i <= n1; i++) {
			for (int j = 1; j <= n2; j++) {
				if (G[i][j] == -1) {
					G[i][j] = ++num;
				}
			}
		}
		for (int i = 1; i <= num; i++) {//初始化每一个顶点的相邻节点
			V[i].clear();
		}

		//横
		for (int i = 1; i <= n1; i++) {
			for (int j = 1; j <= n2 - 1; j++) {
				if (G[i][j] != 0 && G[i][j + 1] != 0) {
					V[G[i][j]].push_back(G[i][j + 1]);
					V[G[i][j + 1]].push_back(G[i][j]);
				}
			}
		}

		//竖
		for (int j = 1; j <= n2; j++) {
			for (int i = 1; i <= n1 - 1; i++) {
				if (G[i][j] != 0 && G[i + 1][j] != 0) {
					V[G[i][j]].push_back(G[i + 1][j]);
					V[G[i + 1][j]].push_back(G[i][j]);
				}
			}
		}

		//最大匹配
		int ans = 0;//初始化解
		memset(match, -1, sizeof(match));//初始化匹配数组
		for (int i = 1; i <= num; i++) {
			memset(vis, 0, sizeof(vis));//初始化访问数组
			if (dfs(i)) ans++;//若存在匹配,则ans++
		}
		cout << ans / 2 << endl;//每个顶点都进行了匹配,因此最后一条边匹配了2次,所以要/2
	}
	return 0;
}

相关例题

zoj1137 Girls and boys

#include<bits/stdc++.h>
using namespace std;
#define maxn 10005

bool vis[maxn];//访问 
int match[maxn];//匹配 
set<int> V[maxn];//记录每个点的邻接顶点集 

bool dfs(int start)
{//深搜的含义是能够找到下一个节点进行匹配
	for (set<int>::iterator it = V[start].begin(); it != V[start].end(); it++) //逐个节点进行分析,分析是否能匹配
	{
		int v = *it;
		if (!vis[v])
		{ //若没有被访问过
			vis[v] = 1;
			int tmp = match[v]; //保留当前的匹配情况
			match[v] = start;   //尝试填写新的匹配关系
			if (tmp == -1 || dfs(tmp))
			{ //分析这个新匹配是否有效
				return true;
			}
			match[v] = tmp;
		}
	}
	return false;
}

int str2int(string str){
	int ans = 0;
	for (int i = 0; i < int(str.size()); i++) {
		ans = ans * 10 + str[i] - '0';
	}
	return ans;
}

int substr(string str) {//获取()里面的数字 
	string s = "";
	for (int i = 0; i < int(str.size()); i++) {
		if (i != 0 && i != int(str.size()) - 1) {
			s += str[i];
		}
	}
	return str2int(s);
}

int main() {
	//建图 
	int n;
	while (cin >> n) {
		for(int i=0;i<n;i++){
			V[i].clear();
		}
		for (int i = 0; i < n; i++) {
			string str;
			cin >> str;//读入0: 
			cin >> str;//读入() 
			int m = substr(str);
			while (m--) {//注意这里,直接改int就不用str转int 
				int nn;
				cin >> nn;
				V[i].insert(nn);
			}
		}
		memset(match, -1, sizeof(match));
		//深搜遍历 
		for (int i = 0; i < n; i++)
		{
			memset(vis, 0, sizeof(vis));
			dfs(i);
		}

		//求出最大匹配数 
		int num = 0; //记录匹配个数
		for (int i = 0; i < n; i++)
		{
			if (match[i] != -1)
				num++;
		}
		cout << n - (num / 2) << endl;
	}
	return 0;
}

zoj1002 Fire Net

#include <iostream>
#include <bits/stdc++.h>
#include <vector>
using namespace std;
#define maxn 100

int Gr[maxn][maxn];
int Gc[maxn][maxn];
char G[maxn][maxn];
vector<int> V[maxn]; //点集
bool vis[maxn];      //标记访问
int match[maxn];     //标记匹配
int cnt2;            //记录最大点

bool dfs(int start)
{                                             //深搜的含义是能够找到下一个节点进行匹配
    for (int i = 0; i < V[start].size(); i++) //逐个节点进行分析,分析是否能匹配
    {
        int v = V[start][i];
        if (!vis[v])
        { //若没有被访问过
            vis[v] = 1;
            int tmp = match[v]; //保留当前的匹配情况
            match[v] = start;   //尝试填写新的匹配关系
            if (tmp == -1 || dfs(tmp))
            { //分析这个新匹配是否有效
                return true;
            }
            match[v] = tmp;
        }
    }
    return false;
}

int main()
{
    int n;
    while (cin >> n && n)
    {
        int cnt1 = 0; //开两个计数器
        for (int i = 1; i <= n; i++)
        {
            for (int j = 1; j <= n; j++)
            {
                cin >> G[i][j];
            }
        }
        for (int i = 1; i <= n; i++)
        {
            for (int j = 1; j <= n; j++)
            {
                if (G[i][j] == 'X')
                {
                    Gr[i][j] = 0; //表示黑格
                    Gc[i][j] = 0;
                }
                else
                {
                    if (j == 1 || (j != 1 && G[i][j - 1] == 'X'))
                        cnt1++;
                    Gr[i][j] = cnt1; //横
                }
            }
        }
        cnt2 = cnt1;
        for (int j = 1; j <= n; j++)
        {
            for (int i = 1; i <= n; i++)
            {
                if (G[i][j] == 'X')
                {
                    continue;
                }
                else
                {
                    if (i == 1 || (i != 0 && G[i - 1][j] == 'X'))
                        cnt2++;
                    Gc[i][j] = cnt2; //列
                }
            }
        }
        for (int i = 1; i <= cnt2; i++)
        {
            V[i].clear();
        }
        for (int i = 1; i <= n; i++)
        {
            for (int j = 1; j <= n; j++)
            {
                if (G[i][j] == 'X')
                    continue;
                else
                {
                    V[Gr[i][j]].push_back(Gc[i][j]);
                    V[Gc[i][j]].push_back(Gr[i][j]);
                }
            }
        }

        //		for(int i=0;i<n;i++){
        //			for(int j=0;j<n;j++){
        //				cout<<Gr[i][j]<<" ";
        //			}
        //			cout<<endl;
        //		}
        //		for(int i=0;i<n;i++){
        //			for(int j=0;j<n;j++){
        //				cout<<Gc[i][j]<<" ";
        //			}
        //			cout<<endl;
        //		}
        //		for(int i=0;i<=cnt2;i++){
        //			for(int j=0;j<V[i].size();j++){
        //				cout<<V[i][j]<<" ";
        //			}
        //			cout<<endl;
        //		}
		memset(match, -1, sizeof(match));
        for (int i = 1; i <= cnt2; i++)
        {
            memset(vis, 0, sizeof(vis));
            dfs(i);
        }
        int num = 0; //记录匹配个数
        for (int i = 1; i <= cnt2; i++)
        {
            if (match[i] != -1)
                num++;
        }
        cout << num / 2 << endl;
    }
    return 0;
}

zoj1654 Place the Robots 

#include<bits/stdc++.h>
using namespace std;
#define maxn 5555

char G[maxn][maxn];
int Gr[maxn][maxn];
int Gc[maxn][maxn];
bool vis[maxn];
int match[maxn];
vector<int> V[maxn];

bool dfs(int start){
	for(int i=0;i<int(V[start].size());i++){
		int v = V[start][i];
		if(!vis[v]){
			vis[v] = 1;
			int tmp = match[v];
			match[v] = start;
			if(tmp==-1 || dfs(tmp)){
				return true;
			}
			match[v] = tmp;
		}
	}
	return false;
}

int main(){
	int m;
	cin>>m;
	for(int w=1;w<=m;w++){
		int r,c;
		cin>>r>>c;
		for(int i=0;i<r;i++){
			cin>>G[i];
		}
		//建立图模型
		int cnt1=0,cnt2;

		//横
		for(int i=0;i<r;i++){
			for(int j=0;j<c;j++){
				if(G[i][j]=='#'){//不能放
					Gc[i][j] = Gr[i][j] = 0;
				}else{
					if(j==0 || (j!=0 && G[i][j-1]=='#')){//若为第一列或不是第一列但前面有#
						cnt1++;
					}
					Gr[i][j] = cnt1;
				}
			}
		}
		
		//竖 
		cnt2 = cnt1;
		for(int j=0;j<c;j++){
			for(int i=0;i<r;i++){
				if(G[i][j]!='#'){
					if(i==0 || (i!=0 && G[i-1][j]=='#')){//若为第一行或不是第一行但上面有#
						cnt2++;
					}
					Gc[i][j] = cnt2;
				}
			}
		}
		
		//建图
		for(int i=0;i<cnt2;i++){
			V[i].clear();
		} 
		
		for(int i=0;i<r;i++){
			for(int j=0;j<c;j++){
				if(G[i][j]=='o'){//只有为o才可放
					V[Gr[i][j]].push_back(Gc[i][j]);
                    V[Gc[i][j]].push_back(Gr[i][j]);
				}
			}
		}
		
		//求最大匹配
		int ans=0;
		memset(match,-1,sizeof(match));
		for(int i=1;i<=cnt2;i++){
			memset(vis,0,sizeof(vis));
			if(dfs(i)){
				ans++;
			}
		}
		
		cout<<"Case :"<<w<<endl;
		cout<<ans/2<<endl;
	} 
	return 0;
}

zoj1516 Uncle Tom's Inherited Land

#include<iostream>
#include<vector>
#include<bits/stdc++.h>
using namespace std;

#define maxn 105

int G[maxn][maxn];
bool vis[maxn];
int match[maxn];
vector<int> V[maxn];

bool dfs(int start) {
	for (int i = 0; i<int(V[start].size()); i++) {
		int v = V[start][i];
		if (!vis[v]) {
			vis[v] = 1;
			int tmp = match[v];
			match[v] = start;
			if (tmp == -1 || dfs(tmp)) {
				return true;
			}
			match[v] = tmp;
		}
	}
	return false;
}

int main() {
	int n1, n2;
	while (cin >> n1 >> n2 && n1 != 0 && n2 != 0) {
		//建图
		memset(G, -1, sizeof(G));
		int m;
		cin >> m;
		while (m--) {
			int a, b;
			cin >> a >> b;
			G[a][b] = 0;
		}
		int num = 0;
		for (int i = 1; i <= n1; i++) {
			for (int j = 1; j <= n2; j++) {
				if (G[i][j] == -1) {
					G[i][j] = ++num;
				}
			}
		}
		for (int i = 1; i <= num; i++) {//初始化V
			V[i].clear();
		}

		//横
		for (int i = 1; i <= n1; i++) {
			for (int j = 1; j <= n2 - 1; j++) {
				if (G[i][j] != 0 && G[i][j + 1] != 0) {
					V[G[i][j]].push_back(G[i][j + 1]);
					V[G[i][j + 1]].push_back(G[i][j]);
				}
			}
		}

		//竖
		for (int j = 1; j <= n2; j++) {
			for (int i = 1; i <= n1 - 1; i++) {
				if (G[i][j] != 0 && G[i + 1][j] != 0) {
					V[G[i][j]].push_back(G[i + 1][j]);
					V[G[i + 1][j]].push_back(G[i][j]);
				}
			}
		}

		//最大匹配
		int ans = 0;
		memset(match, -1, sizeof(match));
		for (int i = 1; i <= num; i++) {
			memset(vis, 0, sizeof(vis));
			if (dfs(i)) ans++;
		}
		cout << ans / 2 << endl;
	}
	return 0;
}

zoj1364 Machine Schedule

#include<bits/stdc++.h>
using namespace std;
#define maxn 205

bool vis[maxn];
int match[maxn];
set<int> V[maxn];

bool dfs(int start){
	for(set<int>::iterator it=V[start].begin();it!=V[start].end();it++){
		int v = *it;
		if(!vis[v]){
			vis[v] = 1;
			int tmp = match[v];
			match[v] = start;
			if(tmp==-1 || dfs(tmp)){
				return true;
			}
			match[v] = tmp;
		}
	}
	return false;
}

int main(){
	int n,m,k;
	while(cin>>n>>m>>k && n){
		//初始化 
		for(int i=0;i<n+m;i++){
			V[i].clear();
		}
		//建图 
		while(k--){
			int a,b,c;
			cin>>a>>b>>c;
			V[b].insert(c+n);
			V[c+n].insert(b);
		}
		
		//最大匹配 
		int ans=0;
		memset(match,-1,sizeof(match));
		for(int i=1;i<n+m;i++){//注意初始时0已经开机 
			memset(vis,0,sizeof(vis));
			if(dfs(i)) ans++;
		}
		cout<<ans/2<<endl;
	}
	return 0;
}

网络流算法(max-flow-min-cut)

构造残存网络

 Ford-Fulkerson算法

算法思路:

推广:

 代码(template):

#include<bits/stdc++.h>
using namespace std;
const int maxn=205;//最大结点数
const int inf=0x7fffffff;

int r[maxn][maxn]; //残留网络,初始化为原图
bool visit[maxn];//是否被访问过
int pre[maxn];//前驱子图,记录了从顶点s到终点的一条可行路径上的其它顶点的前一个顶点,从前驱子图中找到从s到t的一条完整路径
int m,n;//边数,顶点数

bool bfs(int s,int t)  //广度优先寻找一条从s到t的增广路,若找到返回true
{
    int p;
    queue<int> q;//创建一个队列的对象
    memset(pre,-1,sizeof(pre));//初始化前驱子图数组pre
    memset(visit,false,sizeof(visit));//初始化访问数组 
    pre[s]=s;//让源点的前驱节点为本身 
    visit[s]=true;//标记访问 
    q.push(s);//把源点放进队列里面
    while(!q.empty())//当队列不为空,继续找下一个结点
    {
        p=q.front();//取出队首
        q.pop();
        for(int i=1;i<=n;i++)
        {
            if(r[p][i]>0 && !visit[i])//如果从p出发到其他顶点,有残留容量大于0,并且没有被标记
            {
                pre[i]=p;//标记选出点的前驱节点为p 
                visit[i]=true;//标记访问 
                if(i==t) //如果找到汇点就结束
                    return true;
                q.push(i);//接着将该点进栈
            }
        }
    }
    return false;
}

int EK(int s,int t)//计算最大流
{
   int flow=0;
   while(bfs(s,t))//当可以找到时
   {
       int d=inf;
       for(int i=t;i!=s;i=pre[i])//从后往前找 
           d=d<r[pre[i]][i]? d:r[pre[i]][i];//找到增广路径中最小流量
       for(int i=t;i!=s;i=pre[i])//从后往前更新 
       {
           r[pre[i]][i]-=d;//正
           r[i][pre[i]]+=d;//反
       }
       flow+=d;//流量++ 
   }
   return flow;
}
int main()
{
    while(scanf("%d%d",&m,&n)!=EOF)
    {
        int u,v,w;
        memset(r,0,sizeof(r));//初始化残留网络 
        for(int i=0;i<m;i++)
        {
            scanf("%d%d%d",&u,&v,&w);
            r[u][v] = w;
        }
        printf("最大流:");
        printf("%d\n",EK(1,n));
    }
    return 0;
}

相关例题

zoj1734 Power Network

#include<bits/stdc++.h>
using namespace std;
#define maxn 50000
#define MAX 120
int n,np,nc,m;//节点个数 生产点个数 消费点个数 中转点个数 
int cap[MAX][MAX];//边容量 

int EKarp(int s,int t)//s--源点 t---终点 
{
	queue<int> Q;//创建队列 
	int flow[MAX][MAX];//流量 
	int pre[MAX];//前导点 
	int node[MAX];//记录能够到index节点的最大流 
	int maxflow = 0;//初始化最大流为0 
	memset(flow,0,sizeof(flow));//初始化流量数组为0 
	while (true)
	{
		Q.push(s);//将起点放入队列 
		memset(node,0,sizeof(node));//初始化 
		node[s] = maxn;//初始化能够到起点的最大流 
		while(!Q.empty())
		{
			int u = Q.front();//取出队列第一个节点 
			Q.pop();
			for (int v=0; v<=t; v++){//遍历所有点 
				if (!node[v] && cap[u][v]>flow[u][v])//如果v没有被访问过且能够正向流 
				{
					Q.push(v);//假如邻接顶点 
					node[v] = min(node[u], cap[u][v]-flow[u][v]);//更新能够流到v的最大流 
					pre[v] = u;//标记v的前导节点为u 
				}
			}
		}
		if (node[t] == 0) break;//若更新后流向终点的流量无增加,则说明已达到最大流 
		for (int u=t; u!=s; u=pre[u])//流量更新 
		{
			flow[pre[u]][u] += node[t];
			flow[u][pre[u]] -= node[t];
		}
		maxflow += node[t];//最大流更新 
	}
	return maxflow;
}

int main()
{
	int from,to,value;
	while(scanf("%d%d%d%d",&n,&np,&nc,&m)!=EOF)
	{
		memset(cap,0,sizeof(cap));//初始化边容量 
		while (m--)
		{
			scanf(" (%d,%d)%d",&from,&to,&value);
			cap[from][to] = value;
		}
		while (np--)
		{
			scanf(" (%d)%d",&from,&value);
			cap[n][from] = value;//记起点index为 n 
		}
		while (nc--)
		{
			scanf(" (%d)%d",&from,&value);
			cap[from][n+1] = value;//记终点index为 n+1 
		}
		printf("%d\n", EKarp(n,n+1));
	}
	return 0;
}

Dog 路径与点连边

#include <bits/stdc++.h>
using namespace std;
#define maxn 500

vector<int> V[maxn]; //存储图
bool vis[maxn];      //访问
int match[maxn];     //匹配

struct node
{
    int x, y;
} nd[maxn];

double dis(node n1, node n2)
{
    return sqrt(pow(n1.x - n2.x, 2) + pow(n1.y - n2.y, 2));
}

bool dfs(int u)
{
    for (int i = 0; i < int(V[u].size()); i++)
    {
        int v = V[u][i]; //找出相邻结点
        if (!vis[v])
        {
            int tmp1 = match[v]; //保存
            match[v] = u;
            int tmp2 = match[u];
            match[u] = v;
            vis[v] = true;
            if (tmp1 == -1 || dfs(tmp1))
            {
                return true;
            }
            match[v] = tmp1;
            match[u] = tmp2;
        }
    }
    return false;
}

int main()
{
    int n, m;
    while (cin >> n >> m)
    {
        int cnt = 0;
        for (int i = 0; i < n; i++)
        {                                  //输入人的结点
            cin >> nd[cnt].x >> nd[cnt].y; // cnt=0为起点,cnt=n-1为终点
            cnt++;
        }
        for (int i = 0; i < m; i++)
        { //输入狗的结点
            cin >> nd[cnt].x >> nd[cnt].y;
            cnt++;
        }
        //建立图模型
        //初始化
        for (int i = 0; i < maxn; i++)
        {
            V[i].clear();
        }
        //人的每个相邻间隔标结点0~(n-2),狗结点(n-1)~(n+m-2)
        //人的坐标0~(n-1),狗的坐标n~(n+m-1)
        //连边
        for (int i = n; i < n + m; i++)
        { //遍历所有狗结点
            for (int j = 0; j < n - 1; j++)
            { //遍历所有人间隔
                if (dis(nd[j], nd[j + 1]) * 2 >= dis(nd[j], nd[i]) + dis(nd[i], nd[j + 1]))
                { //满足条件,V[j]与V[i-1]连边
                    V[j].push_back(i - 1);
                    V[i - 1].push_back(j);
                }
            }
        }
        //求解最大匹配
        memset(match, -1, sizeof(match));
        int ans = 0;
        for (int i = n - 2; i >= 0; i--)
        {
            memset(vis, 0, sizeof(vis));
            if (dfs(i))
            {
                ans++;
            }
        }
        cout << n + ans << endl;
        for (int i = 0; i < n - 1; i++)
        {
            cout << nd[i].x << " " << nd[i].y << " ";
            if (match[i] != -1)
            {
                cout << nd[match[i] + 1].x << " " << nd[match[i] + 1].y << " ";
            }
        }
        cout << nd[n - 1].x << " " << nd[n - 1].y;
    }
    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值