P4542 [ZJOI2011]營救皮卡丘
阿新 • • 發佈:2022-03-26
題目傳送門
題意
給定一張帶有邊權的圖, \(k\) 個人從 \(0\) 點出發, 只要有一個人到達 \(n\) 號點就結束, 到達每個點之前, 所有編號比它小的點至少要被一個人到達過。求所有人的移動最短總距離。
題解
也許可以看出是網路流?雖然是別人告訴我的
你這麼去考慮其實就簡單很多了: 考慮對於一個人, 他顯然不會往比所在點編號更小的點走,因為這些點都走過了, 如果要走的話的也是因為我往後走再走到一個沒走過的點,路徑會更短。我們預處理出每個點對的最短路, 這樣就可以認為我們每次都會走到一個沒走過的點
假設前 \(x\) 個點已經走過了, 現在要走到 \(x+1\), 那麼這\(k\) 個人肯定停在前\(x\)
考慮建圖, 對於每個點, 先從源點向他連一條費用為\(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; }