帶下界的網絡流和費用流

所謂帶下界的網絡流和費用流,就是對於網絡流和費用流模型的一些邊,除了有容量之外,還有流量下界的限制,即這條邊至少要流過下界這麼多的流量。

在下文中,用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;
	}
}


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章