@bzoj - 3691@ 游行


@description@

每年春季,在某岛屿上都会举行游行活动。
在这个岛屿上有N个城市,M条连接着城市的有向道路。
你要安排英雄们的巡游。英雄从城市si出发,经过若干个城市,到城市ti结束,需要特别注意的是,每个英雄的巡游的si可以和ti相同,但是必须至少途径2个城市。

每次游行你的花费将由3部分构成:
1.每个英雄游行经过的距离之和,需要特别注意的是,假如一条边被途径了k次,那么它对答案的贡献是k*ci,ci表示这条边的边权。
2.如果一个英雄的巡游的si不等于ti,那么会额外增加C的费用。因为英雄要打的回到起点。
3.如果一个城市没有任何一个英雄途经,那么这个城市会很不高兴,需要C费用的补偿。

你有无数个的英雄。你要合理安排游行方案,使得费用最小。
由于每年,C值都是不一样的。所以你要回答Q个询问,每个询问都是,当C为当前输入数值的时候的答案。

原题传送门。

@solution@

如果起点 si = ti,代价为环上边权之和。联想到最小权可相交环覆盖。

我们不妨在所有点对之间连权值为 C 的边(包括自环),则在这个图上任意可相交环覆盖都对应一种合法的方案(自环表示没有人经过)。
因此转化为最小权可相交环覆盖(可以先用 floyd 转化为最小权不相交环覆盖,但不是必须的)。拆点,用上下界网络流可以建图,不细谈。

注意到最终答案总可以表示为 k*C + b (0 <= k <= N)。
只要求出每种 k 对应的最小 b 就可以 O(QN)(或者用二分 O(QlogN))回答询问。

我们构造出来的图是原图 + 费用为 C 的完全图,长得就很有特殊性质。
一条从 S -> x' -> ... -> y -> T 的增广路,中间要么是原图中的一条路径,要么是一条 C 边。
满流时,如果经过 k 条 C 边,则在原图上有 N - k 条增广路。

因此在原图上每次发送单位流量,记录此时的最小费用,就可以找到 k*C + b 中每个 k 对应的 b。

@accepted code@

#include <queue>
#include <cstdio>
#include <algorithm>
using namespace std;

typedef pair<int, int> pii;
#define fi first
#define se second
#define mp make_pair

const int MAXN = 250, MAXC = 10000, INF = int(1E9);

namespace FlowGraph{
	const int MAXV = 2*MAXN, MAXE = 3*MAXN*MAXN;
	
	struct edge{
		int to, cap, flow, cost;
		edge *nxt, *rev;
	}edges[MAXE + 5], *adj[MAXV + 5], *cur[MAXV + 5], *ecnt;
	
	int d[MAXV + 5], h[MAXV + 5], n, s, t;
	void clear(int _n) {
		n = _n;
		for(int i=1;i<=n;i++)
			adj[i] = NULL, h[i] = d[i] = 0;
		ecnt = edges;
	}
	void addedge(int u, int v, int c, int w) {
		edge *p = (++ecnt), *q = (++ecnt);
		p->to = v, p->cap = c, p->flow = 0, p->cost = w;
		p->nxt = adj[u], adj[u] = p;
		q->to = u, q->cap = 0, q->flow = 0, q->cost = -w;
		q->nxt = adj[v], adj[v] = q;
		p->rev = q, q->rev = p;
		
//		printf("! %d %d %d %d\n", u, v, c, w);
	}
	
	int f[MAXV + 5], hp[MAXV + 5];
	
	void update(int x, int k) {
		f[x] = k;
		while( x ) {
			hp[x] = x;
			if( (x<<1) <= n && f[hp[x<<1]] < f[hp[x]] )
				hp[x] = hp[x<<1];
			if( (x<<1|1) <= n && f[hp[x<<1|1]] < f[hp[x]] )
				hp[x] = hp[x<<1|1];
			x >>= 1;
		}
	}
	
	bool relabel() {
		for(int i=1;i<=n;i++)
			h[i] += d[i], f[i] = d[i] = INF, cur[i] = adj[i], hp[i] = i;
			
		update(t, d[t] = 0);
		while( f[hp[1]] != INF ) {
			int x = hp[1]; update(x, INF);
			for(edge *p=adj[x];p;p=p->nxt) {
				int c = p->rev->cost + h[x] - h[p->to];
				if( p->rev->cap > p->rev->flow && d[p->to] > d[x] + c )
					update(p->to, d[p->to] = d[x] + c);
			}
		}
		return d[s] != INF;
	}
	
	bool vis[MAXV + 5];
	int aug(int x, int tot) {
		if( x == t ) return tot;
		vis[x] = true; int sum = 0;
		for(edge *&p=cur[x];p;p=p->nxt) {
			int c = p->cost + h[p->to] - h[x];
			if( d[x] == d[p->to] + c && !vis[p->to] && p->cap > p->flow ) {
				int del = aug(p->to, min(tot - sum, p->cap - p->flow));
				sum += del, p->flow += del, p->rev->flow -= del;
				if( sum == tot ) break;
			}
		}
		vis[x] = false; return sum;
	}
	
	int min_cost_max_flow(int _s, int _t) {
		int cost = 0, flow = 0; s = _s, t = _t;
		while( relabel() ) {
			int del = aug(s, INF);
			flow += del, cost += del * (d[s] + h[s]);
		}
		return cost;
	}
}

int A[MAXN + 5][MAXN + 5], d[MAXN + 5], N, M, Q;
int main() {
	scanf("%d%d%d", &N, &M, &Q);
	for(int i=1;i<=N;i++)
		for(int j=1;j<=N;j++)
			A[i][j] = INF;
	for(int i=1,a,b,c;i<=M;i++)
		scanf("%d%d%d", &a, &b, &c), A[a][b] = min(A[a][b], c);
	
	int s = 2*N + 1, t = 2*N + 2; FlowGraph::clear(t);
	for(int i=1;i<=N;i++) {
		for(int j=1;j<=N;j++)
			if( A[i][j] != INF )FlowGraph::addedge(i + N, j, INF, A[i][j]);
		FlowGraph::addedge(s, i + N, 1, 0);
		FlowGraph::addedge(i, i + N, INF, 0);
		FlowGraph::addedge(i, t, 1, 0);
	}
	
	FlowGraph::s = s, FlowGraph::t = t; int n;
	for(n=1;FlowGraph::relabel()&&n<=N;FlowGraph::aug(s, 1),n++)
		d[n] = d[n - 1] + (FlowGraph::d[s] + FlowGraph::h[s]);
	for(;n<=N;n++) d[n] = INF;

	for(int i=1;i<=Q;i++) {
		int C, ans = INF; scanf("%d", &C);
		for(int j=0;j<=N;j++)
			ans = min(ans, C*(N - j) + d[j]);
		printf("%d\n", ans);
	}
}

@details@

想起「东方文花帖DS-游行圣」

一开始想的是二分找每个 k*C + b 对应的区间 [Cl, Cr],然后发现跑一次费用流都慢得要死 = =。
然后花了一个上午研究费用流怎么写 = =。最后发现思路有问题 = =。

养成好习惯,没事儿别写已死的 spfa。能用势函数转化为 dijkstra 为什么不用呢。

posted @ 2020-06-01 11:21  Tiw_Air_OAO  阅读(116)  评论(0编辑  收藏  举报