@bzoj - 2324@ [ZJOI2011]营救皮卡丘


@description@

皮卡丘被火箭队用邪恶的计谋抢走了!这三个坏家伙还给小智留下了赤果果的挑衅!为了皮卡丘,也为了正义,小智和他的朋友们义不容辞的踏上了营救皮卡丘的道路。
火箭队一共有N个据点,据点之间存在M条双向道路。据点分别从1到N标号。小智一行K人从真新镇出发,营救被困在N号据点的皮卡丘。为了方便起见,我们将真新镇视为0号据点,一开始K个人都在0号点。
由于火箭队的重重布防,要想摧毁K号据点,必须按照顺序先摧毁1到K-1号据点,并且,如果K-1号据点没有被摧毁,由于防御的连锁性,小智一行任何一个人进入据点K,都会被发现,并产生严重后果。因此,在K-1号据点被摧毁之前,任何人是不能够经过K号据点的。
为了简化问题,我们忽略战斗环节,小智一行任何一个人经过K号据点即认为K号据点被摧毁。被摧毁的据点依然是可以被经过的。
K个人是可以分头行动的,只要有任何一个人在K-1号据点被摧毁之后,经过K号据点,K号据点就被摧毁了。显然的,只要N号据点被摧毁,皮卡丘就得救了。
野外的道路是不安全的,因此小智一行希望在摧毁N号据点救出皮卡丘的同时,使得K个人所经过的道路的长度总和最少。
请你帮助小智设计一个最佳的营救方案吧!

Input
第一行包含三个正整数N,M,K。表示一共有N+1个据点,分别从0到N编号,以及M条无向边。一开始小智一行共K个人均位于0号点。
接下来M行,每行三个非负整数,第i行的整数为Ai,Bi,Li。表示存在一条从Ai号据点到Bi号据点的长度为Li的道路。

Output
仅包含一个整数S,为营救皮卡丘所需要经过的最小的道路总和。

Sample Input
3 4 2
0 1 1
1 2 1
2 3 100
0 3 1
Sample Output
3

【样例说明】
小智和小霞一起前去营救皮卡丘。在最优方案中,小智先从真新镇前往1号点,接着前往2号据点。当小智成功摧毁2号据点之后,小霞从真新镇出发直接前往3号据点,救出皮卡丘。

HINT
对于100%的数据满足N ≤ 150, M ≤ 20 000, 1 ≤ K ≤ 10, Li ≤ 10 000, 保证小智一行一定能够救出皮卡丘。
至于为什么K ≤ 10,你可以认为最终在小智的号召下,小智,小霞,小刚,小建,小遥,小胜,小光,艾莉丝,天桐,还有去日本旅游的黑猫警长,一同前去大战火箭队。

@solution@

考虑如果 j 还没有被到达过且 j 之前的都曾被到达过,此时某个人如果想从 i 走到 j,那么必然只会经过编号比 j 小的点。

先考虑我们是否可以预处理出 A[i][j] 表示 i 出发经过编号 ≤ j 的点到达点 j 的最短路径。
可以发现这个 Floyd 的过程非常相似。我们做 Floyd 时最外层循环的含义是“仅经过编号 ≤ k 的点时的最短路”。
于是就可以直接在 Floyd 的时候顺便求出 A[i][j]。

那么在我们求出 A[i][j] 过后,其实就可以只考虑我们第一次经过某个点(因为不是第一次经过肯定就是在第一次经过另外一个点的路上,于是会被算入上面的最短路 A[i][j])。
相当于我们是选择 K 条从 0 号点出发的路径,使得这 K 条路径覆盖完所有点,且 K 条路径长度总和最小(这里的长度就是 A[i][j])。
覆盖完所有点,可以考虑使用拆点 + 上下界网络流。
除了 0 号点的所有点向 0 号点连 inf 边,就可以跑上下界的最小费用无源汇流,此时流出来的费用就是答案。

@accepted code@

#include<cstdio>
#include<algorithm>
using namespace std;
const int MAXN = 150;
const int MAXV = 350;
const int MAXE = 50000;
const int INF = int(1E9);
struct FlowGraph{
	struct edge{
		int to, flow, cap, cost;
		edge *rev, *nxt;
	}edges[MAXE + 5], *adj[MAXV + 5], *cur[MAXV + 5], *ecnt;
	FlowGraph() {ecnt = &edges[0];}
	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 hp[MAXV + 5], f[MAXV + 5];
	int d[MAXV + 5], h[MAXV + 5], s, t;
	void update(int x, int k) {
		f[x] = k;
		while( x ) {
			hp[x] = x;
			if( (x<<1) <= t && f[hp[x<<1]] < f[hp[x]] )
				hp[x] = hp[x<<1];
			if( (x<<1|1) <= t && f[hp[x<<1|1]] < f[hp[x]] )
				hp[x] = hp[x<<1|1];
			x >>= 1;
		}
	}
	void maintain() {
		for(int i=1;i<=t;i++)
			h[i] += d[i];
	}
	bool relabel() {
		maintain();
		for(int i=1;i<=t;i++)
			hp[i] = i, d[i] = f[i] = INF, cur[i] = adj[i];
		d[s] = 0; update(s, 0);
		while( f[hp[1]] != INF ) {
			int x = hp[1]; update(x, INF);
			for(edge *p=adj[x];p;p=p->nxt) {
				int w = p->cost + h[x] - h[p->to];
				if( p->cap > p->flow && d[x] + w < d[p->to] ) {
					d[p->to] = d[x] + w;
					update(p->to, d[p->to]);
				}
			}
		}
		return !(d[t] == INF);
	}
	bool vis[MAXV + 5];
	int aug(int x, int tot) {
		if( x == t ) return tot;
		int sum = 0; vis[x] = true;
		for(edge *&p=cur[x];p;p=p->nxt) {
			int w = p->cost + h[x] - h[p->to];
			if( !vis[p->to] && p->cap > p->flow && d[x] + w == d[p->to] ) {
				int del = aug(p->to, min(tot - sum, p->cap - p->flow));
				p->flow += del, p->rev->flow -= del, sum += del;
				if( sum == tot ) break;
			}
		}
		vis[x] = false;
		return sum;
	}
	int min_cost_max_flow(int _s, int _t) {
		s = _s, t = _t; int cost = 0;
		while( relabel() ) {
			int del = aug(s, INF);
			cost += del*(d[t] + h[t]);
		}
		return cost;
	}
}G;
int N, M, K;
int A[MAXN + 5][MAXN + 5];
int main() {
	scanf("%d%d%d", &N, &M, &K), N++;
	int s = 2*N + 1, t = 2*N + 2;
	for(int i=1;i<=N;i++)
		for(int j=1;j<=N;j++)
			A[i][j] = (i == j ? 0 : INF);
	for(int i=2;i<=N;i++) {
		G.addedge(i, t, 1, 0);
		G.addedge(i, i + N, 1, 0);
		G.addedge(s, i + N, 1, 0);
		G.addedge(i + N, 1, INF, 0);
	}
	G.addedge(1, 1 + N, K, 0);
	for(int i=1;i<=M;i++) {
		int u, v, w; scanf("%d%d%d", &u, &v, &w), u++, v++;
		A[u][v] = min(A[u][v], w), A[v][u] = min(A[v][u], w);
	}
	for(int k=1;k<=N;k++) {
		for(int i=1;i<=N;i++)
			for(int j=1;j<=N;j++)
				A[i][j] = min(A[i][j], A[i][k] + A[k][j]);
		for(int i=1;i<k;i++)
			G.addedge(i + N, k, INF, A[i][k]);
	}
	printf("%d\n", G.min_cost_max_flow(s, t));
}

@details@

代码中为了更严谨,我将拆点后的中间的边不仅下界设为 1,上界也设为 1(即保证每个点最多只能被一个人“第一次经过”)。

但其实。。。好像也没有这个必要。因为假如路径相交,一定可以将它拆成路径不相交的情况。

posted @ 2019-08-22 20:30  Tiw_Air_OAO  阅读(134)  评论(0编辑  收藏  举报