HDU 1532 Drainage Ditches

版权声明:本人QQ 249712949 欢迎交流算法(请备注)。 https://blog.csdn.net/coord_/article/details/80569635

传送门

网络流、最大流问题入门,基础的增广路算法(EK),首先定义一下该问题,主要是一个有向图,有两个特殊点-源点和汇点,每条边上有一个容量,在满足两个条件:

  • 为每条边输入一个流量,且流量小于等于容量
  • 以流量来计,源点只有出度,汇点只有入度,其他点入度等于出度

的情况下,尽可能多地往图里面输入流量,求整个图所有边的流量之和的最大值。
该算法的思想就是建图以后,每次在图里面找一条从源点到汇点的能使总流量增大的路(简单路,没环),每次就找一条,然后循环不停地找直到找不到为止,需要每次找完以后更新边的流量。每次找路用BFS,需要记录每个点的前驱点和当前进度的可行流量值,且保证每个点只记录一次当前可行的值。然后的问题是需要理解每条边的反方向的容量和流量的意义,首先所有边包括反方向的容量都不会变(反方向边容量都是0),所有边包括反方向的流量初始也都是0,需要变的是流量,正常边的流量加上相应数值,反方向边的流量减去相应数值。

#include <iostream>
#include <vector>
#include <queue>
#include <cstring>
using namespace std;

typedef long long LL;
#define oo 1e9

struct Edge
{
	int from;
	int to;
	int cap;
	int flow;
};
vector<Edge> ve;
vector<int> v[201];
int a[201];                   // 存放从源点到当前点的可行流量值
int pre[201];                 // 存放当前点的前驱边(前驱边在边表里的索引)

void add_edge(int f, int t, int c)
{
	ve.push_back({ f, t, c, 0 });
	ve.push_back({ t, f, 0, 0 });
	v[f].push_back(ve.size() - 2);
	v[t].push_back(ve.size() - 1);          // 反向边,这两个互逆的边的下标可以通过和1异或来互相取得
}

void init(int t)
{
	for (int i = 1; i <= t; i++)
		v[i].clear();
	ve.clear();
}

LL max_flow(int t)
{
	LL flow = 0;
	for (;;)
	{
		memset(a, 0, sizeof a);
		queue<int> q;
		q.push(1);
		a[1] = oo;           // 从源点开始的流量无限
		for (; !q.empty();)
		{
			int x = q.front();
			q.pop();
			for (int i = 0; i < v[x].size(); i++)
			{
				Edge& e = ve[v[x][i]];
				if (!a[e.to] && e.flow < e.cap)         // 确保没访问过,找一条源点到汇点的简单路,不形成环
				{
					pre[e.to] = v[x][i];                // 存放前驱边
					a[e.to] = min(a[x], e.cap - e.flow);
					q.push(e.to);
				}
			}
			if (a[t]) break;                            // 汇点流量值已被更新,说明已找到一条到汇点的路
		}
		if (!a[t]) break;                               // 整个BFS都到不了汇点,总流量值不可增加,结束。
		for (int i = t; i != 1; i = ve[pre[i]].from)    // 更新边上的流量,为下次找路作准备
		{
			ve[pre[i]].flow += a[t];                    // 是刚刚找的路上的边(不一定是原图输入时的正向边)
			ve[pre[i] ^ 1].flow -= a[t];                // 是刚刚找的路上的边的反向边(不一定是原图输入时的反向边)
		}                                             // 这里和1异或就是对二进制最末尾取反,正好0变成1,1变成0,2变成3,3变成2......
		flow += a[t];
	}
	return flow;
}

int main()
{
	int N, M;                       // N为边数,M为点数 1~M    源点1,汇点M
	for (; cin >> N >> M;)  
	{
		init(M);
		int f, t, c;
		for (int i = 0; i < N; i++)
		{
			cin >> f >> t >> c;
			add_edge(f, t, c);
		}
		cout << max_flow(M) << endl;
	}

    return 0;
}

猜你喜欢

转载自blog.csdn.net/coord_/article/details/80569635
今日推荐