1. 程式人生 > 其它 >P4542 [ZJOI2011]營救皮卡丘

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;
}