@bzoj - 3130@ [Sdoi2013]费用流


@description@

对于一张给定的运输网络,Alice先确定一个最大流,如果有多种解,Alice可以任选一种;
之后Bob在已知Alice的方案的前提下,每条边上分配单位花费(单位花费必须是非负实数),要求所有边的单位花费之和等于P。
总费用等于每一条边的实际流量乘以该边的单位花费,Alice希望总费用尽量小,而Bob希望总费用尽量大。如果两个人都执行最优策略,最大流的值和总费用分别为多少。

Input
第一行三个整数N,M,P。N表示给定运输网络中节点的数量,M表示有向边的数量,P的含义见问题描述部分。为了简化问题,我们假设源点S是点1,汇点T是点N。
接下来M行,每行三个整数A,B,C,表示有一条从点A到点B的有向边,其最大流量是C。

Output
第一行一个整数,表示最大流的值。
第二行一个实数,表示总费用。建议选手输出四位以上小数。

Sample Input
3 2 1
1 2 10
2 3 15
Sample Output
10
10.0000

HINT
【样例说明】
对于Alice,最大流的方案是固定的。两条边的实际流量都为10。
对于Bob,给第一条边分配0.5的费用,第二条边分配0.5的费用。总费用为:100.5+100.5=10。可以证明不存在总费用更大的分配方案。

【数据规模和约定】
对于20%的测试数据:所有有向边的最大流量都是1。
对于100%的测试数据:N < = 100,M < = 1000。
对于l00%的测试数据:所有点的编号在I..N范围内。1 < = 每条边的最大流量 < = 50000。1 < = P < = 10。给定运输网络中不会有起点和终点相同的边。

@solution@

最大流随便跑跑就可以流出来。

考虑最小费用部分,首先 Bob 肯定是把所有费用全部压在流量最大的那条边,故 Alice 选择的最大流方案一定是要所有边流量的最大值尽可能小。
最大值尽可能小,不难想到二分。我们二分出边流量的最大值 x,将边的容量 c 改为 min(c, x),在新图上跑最大流看是否等于原图的最大流即可。

这道题启示我们有些最小化边流量的最大值可以采用二分的方法(虽然我觉得二分套网络流的复杂度很玄)

@accepted code@

#include<cstdio>
#include<queue>
using namespace std;
const int MAXN = 100;
const int MAXM = 1000;
const int MAXV = MAXN;
const int MAXE = MAXM*2;
const double INF = 1E9;
struct FlowGraph{
	struct edge{
		int to; double cap, flow;
		edge *nxt, *rev;
	}edges[MAXE + 5], *adj[MAXV + 5], *ecnt;
	int d[MAXV + 5], vd[MAXV + 5], s, t;
	void clear(int n) {
		for(int i=0;i<=n+3;i++)
			d[i] = vd[i] = 0, adj[i] = NULL;
		ecnt = &edges[0];
	}
	void addedge(int u, int v, double c) {
		edge *p = (++ecnt), *q = (++ecnt);
		p->to = v, p->cap = c, p->flow = 0;
		p->nxt = adj[u], adj[u] = p;
		q->to = u, q->cap = 0, q->flow = 0;
		q->nxt = adj[v], adj[v] = q;
		p->rev = q, q->rev = p;
	}
	queue<int>que;
	void get_dist() {
		for(int i=s;i<=t;i++)
			d[i] = t + 3;
		d[t] = 0; que.push(t);
		while( !que.empty() ) {
			int f = que.front(); que.pop(); vd[d[f]]++;
			for(edge *p=adj[f];p;p=p->nxt)
				if( p->rev->cap > p->rev->flow )
					if( d[f] + 1 < d[p->to] ) {
						d[p->to] = d[f] + 1;
						que.push(p->to);
					}
		}
	}
	double aug(int x, double tot) {
		if( x == t ) return tot;
		double sum = 0; int mind = t + 3;
		for(edge *p=adj[x];p;p=p->nxt) {
			if( p->cap > p->flow ) {
				if( d[p->to] + 1 == d[x] ) {
					double del = aug(p->to, min(tot - sum, p->cap - p->flow));
					p->flow += del, p->rev->flow -= del, sum += del;
					if( sum == tot || d[s] >= t + 3 ) return sum;
				}
				mind = min(mind, d[p->to]);
			}
		}
		if( sum == 0 ) {
			vd[d[x]]--;
			if( vd[d[x]] == 0 ) {
				d[s] = t + 3;
				return 0;
			}
			d[x] = mind + 1;
			vd[d[x]]++;
		}
		return sum;
	}
	double max_flow(int _s, int _t) {
		s = _s, t = _t; get_dist();
		double flow = 0;
		while( d[s] < t + 3 )
			flow += aug(s, INF);
		return flow;
	}
}G;
int a[MAXM + 5], b[MAXM + 5]; double c[MAXM + 5];
int N, M, P; double mf;
bool check(double x) {
	G.clear(N);
	for(int i=1;i<=M;i++)
		G.addedge(a[i], b[i], min(c[i], x));
	return G.max_flow(1, N) == mf;
}
int main() {
	double le = 0, ri = -INF;
	scanf("%d%d%d", &N, &M, &P);
	G.clear(N);
	for(int i=1;i<=M;i++) {
		scanf("%d%d%lf", &a[i], &b[i], &c[i]);
	 	G.addedge(a[i], b[i], c[i]);
	 	ri = max(ri, c[i]);
	}
	mf = G.max_flow(1, N);
	for(int i=0;i<60;i++) {
		double mid = (le + ri) / 2;
		if( check(mid) ) ri = mid;
		else le = mid;
	}
	printf("%.0lf\n%lf\n", mf, ri*P);
}

@details@

一开始 WA 了,调了半天调不出错来,又觉得思路没有问题。

最后发现原来流量可以为小数但是我写的整数二分(所以完全没有分析为什么输出结果要保留小数)

posted @ 2019-08-22 19:12  Tiw_Air_OAO  阅读(144)  评论(0编辑  收藏  举报