• @bzoj



    @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
    ", 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
    ", G.min_cost_max_flow(s, t));
    }
    

    @details@

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

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

  • 相关阅读:
    【前端积累】点击链接切换图片显示
    【Spring Boot && Spring Cloud系列】构建Springboot项目 实现restful风格接口
    【JavaEE企业应用学习记录】验证配置
    optiontransferselect例子
    javascript面向对象(给对象添加属性和方法的方式)
    JavaBean toString方式
    JavaScript 字符串操作
    SQL AND和OR求值顺序
    dom4j解析XML
    SQL使用总结-like,MAX,MIN
  • 原文地址:https://www.cnblogs.com/Tiw-Air-OAO/p/11396666.html
Copyright © 2020-2023  润新知