带下界的网络流和费用流

所谓带下界的网络流和费用流,就是对于网络流和费用流模型的一些边,除了有容量之外,还有流量下界的限制,即这条边至少要流过下界这么多的流量。

在下文中,用x->y:[l,r]表示网络流模型中一条x连向y,容量为r,下界为l的边;用x->y:[l,r],c表示费用流模型中一条x连向y,容量为r,下界为l,费用为c的边。如果没有下界,则把[l,r]改为w。无穷大为INFI。源和汇分别为S和T。


1、带下界的可行流

这方面的论文好像有不少,做法大致有两种。一种是基于二分的,这种我还不清楚,有兴趣的自己去搜搜看吧……另一种就是重新构图的方法。

新建超级源S_和超级汇T_(本想叫S'和T'的,但发现看不清……)。连边:T->S:INFI。对于每条有下界的边x->y:[l,r],连边:S_->y:l、x->T_:l、x->y:r-l。求从S_到T_的最大流,如果S_的每条出边都满流,那么就存在可行流。

下面我们来说明这个构图方法的正确性。这个构图的思路是,强制让所有下界满流。如果我们直接让所有有下界的边的流量都达到了下界,这时候流量平衡条件会被破坏,那么我们就需要“补”一些流量。假设整个模型中只有一条边x->y:[l,r]有下界,那么我们让它的流量达到下界,此时y会多出l的流量需要流走,而x会有l的流量需要流入。注意我们是无法直接让这条边的流量达到下界的,那么我们就建超级源和超级汇来提供这些流量。所以就有S_->y:l、x->T_:l。但是这样y还是没法流到x,所以再连T->S:INFI,这样原来的源和汇就变成了满足流量平衡的普通节点。

我们可以这么理解:如果一条边x->y:[l,r]的下界可以被满足,那么从y到汇一定存在增广路可以流掉这l的流量,同样从源到x也一定存在这样的增广路。那么按照这个方法构图就可以求出是否存在这种增广路。

而为了保证下界不会被退流,原来x->y:[l,r]的边就被改成了x->y:r-l。


2、最大可行流

无论是求最大还是最小可行流,都是在求完可行流的残量网络上操作的。网络流模型的最大可行流即残量网络上原来的源到汇的最大流。

我们先不管T->S的边退回的流量。这样残量网络就是一个满足所有下界的网络,而且下界不会退流。那么在这个网络上求最大流得到的一定是除开下界产生的流量的的最大可行流。

那么下界的流量呢?事实上就是沿T->S流的流量,也就是T->S被退回的流量。如果可行流的流量没有经过T->S的边,那么就一定是流到了某个直接和T_相连的点,就直接流到T_了。在最初的模型中,也即从源出发的流量在流到汇之前经过了这两条边,也就只会计算一次流量。所以下界产生的流量实际上也就是T->S的流量。

这个方法是可以构造方案的。


3、最小可行流

网络流模型的最小可行流即T->S边的流量,减去,删去T->S的边后,从T到S的最大流。

为什么最小可行流不直接是T->S的流量?因为图中可能会存在从S到T再回到S的循环流。我们可以用管道的模型来比喻。一根管道的流量即每单位时间通过的水量,最大流也即源点每单位时间要供出去的水量。而对于一个循环流,我们只需在最开始的时候向源点供一点水,这点水就可以一直在循环的管道中流,而不需每单位时间都供水,所以这部分虽然有流量,但却是不应被计入最大流的部分。

这么说来好像平时求的一般向的最大流也不是最大流?事实上一般建出的网络流模型是不会存在循环流的,一般来说只有故意卡循环流的题目才会需要这样处理……

还要注意的一点是,T到S的最大流有可能比T->S的流量还要大,一减就变成负的流量了。而最大流是不能为负的。那么我们就再从S到T求一次最大流,但是注意只给S供应   恰好   T到S的最大流比T->S的流量   多出的流量(注意断句)。就相当于我们把源点视为一个普通的节点(不然就变成最大可行流了嘛……)。

这个方法也是可以构造方案的。


4、最小费用最大可行流

其实费用流在处理上和网络流是有一点。建模也是按照网络流的方法建(新建的边费用都为0,x->y:r-l的边费用为原费用),最小费用最大可行流就按照最大可行流的方法求,把求最大流改为求最小费用最大流就行了。



题目:

SGU173。给定网络,求能否使某些边满流,并求此时的最小可行流,并输出方案。

直接按照上面说的方法做就好了。代码:

//SGU176; Flow construction; Flow Network with Lower Bounds
#include <cstdio>
#include <cstdlib>
#include <algorithm>
#define N 300
#define M 20000
#define INFI 12345678

struct edge
{
	int next, node, w;
}e[M << 1 | 1];
struct inedge
{
	int x, y, w, c;
}ie[M + 1];
int head[N + 1], tot = 1;
int n, m, x, y, w, c, S, T, S_, T_, ans, d[N + 1], q[N + 1], flow[M + 1], sum = 0;
int in[N + 1], out[N + 1];
bool ok = true;

inline void addedge(int a, int b, int w)
{
	e[++tot].next = head[a];
	head[a] = tot, e[tot].node = b, e[tot].w = w;
	e[++tot].next = head[b];
	head[b] = tot, e[tot].node = a, e[tot].w = 0;
}

bool bfs(int S, int T)
{
	int h = 0, t = 0;
	for (int i = 1; i <= N; ++i) d[i] = 0;
	d[S] = 1, q[t++] = S;
	while (h < t)
	{
		int cur = q[h++];
		for (int i = head[cur]; i; i = e[i].next)
		{
			if (!e[i].w) continue;
			int node = e[i].node;
			if (d[node]) continue;
			d[node] = d[cur] + 1;
			q[t++] = node;
		}
	}
	return d[T];
}

int dfs(int x, int inflow, int T)
{
	if (x == T || !inflow) return inflow;
	int ret = inflow, flow;
	for (int i = head[x]; i; i = e[i].next)
	{
		int node = e[i].node;
		if (d[node] != d[x] + 1) continue;
		flow = dfs(node, std::min(e[i].w, ret), T);
		if (!flow) continue;
		e[i].w -= flow, e[i ^ 1].w += flow;
		ret -= flow;
		if (!ret) break;
	}
	if (ret == inflow) d[x] = -1;
	return inflow - ret;
}

inline int maxFlow(int S, int T)
{
	int ret = 0;
	while (bfs(S, T))
		ret += dfs(S, INFI, T);
	return ret;
}

int main()
{
#ifdef KANARI
	freopen("input.txt", "r", stdin);
	freopen("output.txt", "w", stdout);
#endif
	scanf("%d%d", &n, &m);
	S = 1, T = n, S_ = n + 1, T_ = n + 2;
	for (int i = 1; i <= m; ++i)
	{
		scanf("%d%d%d%d", &x, &y, &w, &c);
		if (c)
		{
			addedge(x, y, 0);
			sum += w, in[y] += w, out[x] += w;
		}
		else addedge(x, y, w);
		ie[i].x = x, ie[i].y = y, ie[i].w = w, ie[i].c = c;
	}
	for (int i = 1; i <= n; ++i)
	{
		addedge(S_, i, in[i]);
		addedge(i, T_, out[i]);
	}
	addedge(T, S, INFI);
	
	if (!n) ok = false;
	if (sum != maxFlow(S_, T_)) ok = false;
	
	ans = e[head[T] ^ 1].w;
	e[head[T]].w = e[head[T] ^ 1].w = 0;
	ans -= maxFlow(T, S);
	
	if (ans < 0)
	{
		e[head[T]].w = e[head[T] ^ 1].w = 0;
		addedge(S_, S, -ans);
		maxFlow(S_, T);
		ans = 0;
	}
	
	for (int i = 1; i <= m; ++i)
		flow[i] = e[i * 2 ^ 1].w + ie[i].w * ie[i].c;
	if (!ok) printf("Impossible\n");
	else
	{
		printf("%d\n", ans);
		for (int i = 1; i < m; ++i) printf("%d ", flow[i]);
		printf("%d\n", flow[m]);
	}
	return 0;
}

TCO09 Championship Round D1L2

给定序列A,记A的元素个数为N。
称一次变换为:选定区间[i,j],满足1≤i≤j≤N,对序列A在区间中的每个数减1,如果已经为0则不操作。这样一次变换的代价为j-i+1。
对于给定序列A,最大变换次数K和最大代价和M,求:在使用不超过K次变换,总变换代价不超过M的前提下,A中最大元素的最小值是多少。
N≤250。

官网有一种二分答案+贪心判断的做法,但是我没看懂……于是就用了Petr的神费用流方法。

首先二分答案,把问题转换成判定性问题。
现在的问题是,能否在限制下使A中最大元素不超过一个值P。
容易发现,变换的顺序并不影响最后的结果。
对于A[i],变换的最小代价应为max(A[i]-P,0)。那么我们令B[i]=max(A[i]-P,0)。
那么限制条件实际上就是要确定不超过K条线段,使得数轴上i的位置被覆盖了至少B[i]次,且线段总长不超过M。

据此可以建立下面的网络流模型:
建立标号为1到N+1的点,以及S′和T′节点。
从源向S′连一条容量为K的边、从T′向汇连一条容量为K、费用为0的边。
从标号为i的点向标号为i+1的点连一条容量为∞、容量下界为a[i]-P、费用为1的边。
从S′向1~N号节点、从2~N+1号节点向T′连一条容量为∞、费用为0的边。
一次变换,即一条线段[i,j],在网络中会是源→S'→i→i+1→…→j→T′→汇的一条增广路。
如果最小费用最大可行流得到的费用不超过M,那么答案合法。

按照上面的方法求最小费用最大可行流即可。

代码:
class ArrayTransformations
{
public:
#define N 250
	string s;
	int a[N + 1], n, x;

#define EDGE 10000
#define NODE 1000
#define INFI 123456789
	struct edge
	{
		int next, node, w, c;
	}e[EDGE + 1];
	int head[NODE + 1], tot;
	int d[NODE + 1], q[NODE + 1], f[NODE + 1], S, T, S_, T_, k, m, nodecnt;
	bool inq[NODE + 1];

	inline void addedge(int a, int b, int w, int c, int w2 = 0)
	{
		e[++tot].next = head[a];
		head[a] = tot, e[tot].node = b, e[tot].w = w, e[tot].c = c;
		e[++tot].next = head[b];
		head[b] = tot, e[tot].node = a, e[tot].w = w2, e[tot].c = -c;
//		printf("%d %d %d %d\n", a, b, w, c);
	}

	inline int inc(int &x)
	{ return x = x + 1 == NODE ? 0 : x + 1; }

	bool SPFA(int S, int T)
	{
		int h = 0, t = 0;
		for (int i = 0; i < nodecnt; ++i) d[i] = INFI, inq[i] = false;
		q[inc(t)] = S, inq[S] = true, d[S] = 0;
		while (h != t)
		{
			int cur = q[inc(h)];
			inq[cur] = false;
			for (int i = head[cur]; i; i = e[i].next)
			{
				if (!e[i].w) continue;
				int node = e[i].node;
				if (d[node] > d[cur] + e[i].c)
				{
					d[node] = d[cur] + e[i].c;
					f[node] = i;
					if (!inq[node])
						inq[node] = true, q[inc(t)] = node;
				}
			}
		}
		return d[T] != INFI;
	}

	inline int costFlow(int S, int T)
	{
		int w, ret = 0;
		while (SPFA(S, T))
		{
			w = INFI;
			for (int x = T; x != S; x = e[f[x] ^ 1].node)
				w = std::min(w, e[f[x]].w);
			for (int x = T; x != S; x = e[f[x] ^ 1].node)
				e[f[x]].w -= w, e[f[x] ^ 1].w += w;
			ret += w * d[T];
		}
		return ret;
	}

	int in[NODE + 1], out[NODE + 1];

	inline bool check(int x)
	{
		int cost = 0;
		for (int i = 1; i <= n; ++i)
			if (a[i] > x)
			{
				cost += a[i] - x;
				if (a[i] - x > k) return false;
			}
		if (cost > m) return false;
		memset(head, 0, sizeof head);
		tot = 1;
		S = 0, T = n + 2;
		int SS = n + 3, TT = n + 4;
		S_ = n + 5, T_ = n + 6;
		nodecnt = n + 7;
		addedge(S, SS, k, 0);
		addedge(TT, T, k, 0);
		for (int i = 1; i <= n; ++i)
			addedge(SS, i, INFI, 0);
		for (int i = 2; i <= n + 1; ++i)
			addedge(i, TT, INFI, 0);
		memset(in, 0, sizeof in), memset(out, 0, sizeof out);
		for (int i = 1; i <= n; ++i)
		{
			if (a[i] > x) in[i + 1] += a[i] - x, out[i] += a[i] - x;
			addedge(i, i + 1, INFI, 1);
		}
		for (int i = 1; i <= n + 1; ++i)
		{
			addedge(S_, i, in[i], 0);
			addedge(i, T_, out[i], 0);
		}
		addedge(T, S, INFI, 0);
		cost += costFlow(S_, T_);
		return cost <= m;
	}

	int minimalValue(vector <string> initialArray, int K, int M)
	{
		k = K, m = M;
		s = "";
		for (int i = 0; i < initialArray.size(); ++i)
			s += initialArray[i];
		memset(a, 0, sizeof a);
		n = 0;
		stringstream ss(s);
		while (ss >> x) a[++n] = x;

		int l = 0, r = 0;
		for (int i = 1; i <= n; ++i) r = max(r, a[i]);
		while (l < r)
		{
			int mid = l + r >> 1;
			if (check(mid)) r = mid;
			else l = mid + 1;
		}
		return l;
	}
}


你可能感兴趣的:(网络流,topcoder,费用流)