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

寵の児 提交于 2019-11-28 06:11:57

@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(即保证每个点最多只能被一个人“第一次经过”)。

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

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!