P4542 [ZJOI2011]营救皮卡丘

题目传送门

题意

给定一张带有边权的图, \(k\) 个人从 \(0\) 点出发, 只要有一个人到达 \(n\) 号点就结束, 到达每个点之前, 所有编号比它小的点至少要被一个人到达过。求所有人的移动最短总距离。

题解

也许可以看出是网络流?虽然是别人告诉我的

你这么去考虑其实就简单很多了: 考虑对于一个人, 他显然不会往比所在点编号更小的点走,因为这些点都走过了, 如果要走的话的也是因为我往后走再走到一个没走过的点,路径会更短。我们预处理出每个点对的最短路, 这样就可以认为我们每次都会走到一个没走过的点

假设前 \(x\) 个点已经走过了, 现在要走到 \(x+1\), 那么这\(k\) 个人肯定停在前\(x\)个点中的某些位置,我们要选出一个人让它走到\(x+1\), 我们管这种行为叫做把\(y\)点的人拿到\(x+1\), 那么显然如果我们把 \(y\)点拿走了, 其他点就不能再拿 \(y\) 点, 同样对于每个点而言, 我们必须要选一个前面的点上面的人, 把他拿到这个点。

考虑建图, 对于每个点, 先从源点向他连一条费用为\(0\),容量为\(1\)的边, 表示一定从前面选一个点走到这, 这个流流到那个点, 就表示把那个点上的人拿过来, 然后对于每个点拆一个备份点出来(防止这个点的入流直接从这个点流出去)。然后考虑: 对于每个备份点往汇点连一条边,假如有流从这条边流过, 就代表这个备份点对应的点被拿到了后面, 由于只能拿一次, 所以流量为\(1\)

对于每个点,我们向所有编号比他小的点的备份点连一条边,如果有流流经这条边,就表示把备份点上的人移动到这条边的起点, 费用为他们的最短距离。

一些细节是,第一个点向汇点的流量是\(k\),表示起点有\(k\)个人,可以拿\(k\)次。

这样做是对的, 在满足最大流的前提下, 每个点都会流出一个流量, 表示每个点都会从前面选择一个点,让这个点上的人走过来,由于肯定会有一个人走到这个点, 上面肯定会有人, 又由于这个点的出流只有\(1\), 所以它只会走一次。

摆。

实现

可能会有重边啊啊啊!!!!

#include <iostream>
#include <cstdio>
#include <vector>
#include <queue>
#define ll long long
using namespace std;

int read(){
    int num=0, flag=1; char c=getchar();
    while(!isdigit(c) && c!='-') c=getchar();
    if(c == '-') c=getchar(), flag=-1;
    while(isdigit(c)) num=num*10+c-'0', c=getchar();
    return num*flag;
}

const int N = 2000;
const int M = 1600000;
const ll inf = 0x3f3f3f3f3f3f3f;

ll ans=0, acost=0;
int n, m, K, s, t;

struct Edge{
    int u, v; ll w, c;
}e[M]; int sz=1, nsz=0, head[N], nxt[M];

void addEdge(int u, int v, int w, int c){
    nsz = max(nsz, max(u, v));
    e[++sz].u=u, e[sz].v=v, e[sz].w=w, e[sz].c=c, nxt[sz]=head[u], head[u]=sz;
    e[++sz].u=v, e[sz].v=u, e[sz].w=0, e[sz].c=-c, nxt[sz]=head[v], head[v]=sz;
}

ll flow[N], cost[N]; int vis[N], lst[N];
void spfa(){
    for(int i=1; i<=nsz; i++) flow[i]=0, cost[i]=inf, vis[i]=0;
    queue<int> q; q.push(s); cost[s]=0, flow[s]=inf;
    while(!q.empty()){
        int x = q.front(); q.pop(); vis[x]=0;
        for(int i=head[x]; i; i=nxt[i]){
            int nex = e[i].v;

            if(cost[x] + e[i].c < cost[nex] && e[i].w){
                flow[nex] = min(flow[x], e[i].w);
                cost[nex] = cost[x] + e[i].c;
                lst[nex] = i;
                if(!vis[nex]) {
                    q.push(nex);
                    vis[nex] = 1;
                }
            }

        }
    }
}

void mcmf(){
    while(true){
        spfa();
        if(flow[t] == 0) return ;
        int x = t; ans += flow[t], acost += flow[t]*cost[t];
        while(x != s){
            e[lst[x]].w -= flow[t];
            e[1^lst[x]].w += flow[t];
            x = e[lst[x]].u;
        }
    }
}

ll f[205][206][206];

int main(){
    n=read()+1, m=read(), K=read();
    for(int i=1; i<=n; i++)
        for(int j=1; j<=n; j++)
            f[i][j][0] = inf;
    for(int i=1; i<=m; i++){
        int u=read()+1, v=read()+1, w=read();
        f[u][v][0] = f[v][u][0] = min(f[u][v][0], 1ll*w);
    }
    for(int i=1; i<=n; i++){
        for(int j=1; j<=n; j++){
            for(int k=1; k<=n; k++){
                f[j][k][i] = min(f[j][k][i-1], f[j][i][i-1]+f[i][k][i-1]);
            }
        }
    }
    addEdge(1, 2*n+2, K, 0);
    for(int i=2; i<n; i++) addEdge(i, 2*n+2, 1, 0);
    for(int i=2; i<=n; i++) addEdge(2*n+1, i+n, 1, 0);
    for(int i=2; i<=n; i++) {
        for(int j=1; j<i; j++){
            if(f[j][i][i]<inf) addEdge(i+n, j, 1, f[j][i][i]);
        }
    }
    s=2*n+1, t=2*n+2;
    mcmf();
    printf("%lld\n", acost);
	return 0;
}
posted @ 2022-03-26 20:26  ltdJcoder  阅读(20)  评论(0编辑  收藏  举报