UOJ #455 [UER #8]雪灾与外卖 (贪心、模拟费用流)
題目鏈接
http://uoj.ac/contest/47/problem/455
題解
模擬費用流,一個非常神奇的東西。
本題即為WC2019 laofu的講課中的Problem 8,經典的老鼠進洞模型,洞有容量和額外權值。
這道題的Subtask 4,5,6,7分別對應著老鼠進洞的最基礎模型、洞有額外權值、洞有容量、洞有容量和額外權值四個變形。
讓我們從最簡單的開始各個擊破。
Subtask 4
注: 本部分配合WC2019課件里的代碼圖理解效果更佳。
數軸上有\(n\)只老鼠(坐標\(x_i\))和\(m\)個洞(坐標\(y_i\)),每個老鼠必須進一個洞,每個洞至多進一只老鼠,最小化距離和。
假設洞\(i\)匹配了老鼠\(j\), 則若\(y_i<x_j\)代價為\(x_j+(-y_i)\), 否則為\((-x_j)+y_i\),不妨拆成\(i,j\)兩部分分別計算。
使用可撤銷貪心的策略。
維護一個老鼠堆和一個洞堆,從左到右掃描。
若當前掃到一只老鼠\(i\),那么先隨便匹配一個目前空閑的洞(不妨假設負無窮遠處有無窮多個洞),從洞堆中取出一個元素\(j\)匹配,其代價為\(x_i+j\)
考慮當后面插入洞的時候允許老鼠反悔(像極了費用流的反向邊)而匹配新的洞,那么如果一個老鼠反悔而匹配了右面的新洞\(k\), 那么代價的增量為\((y_k-x_i)-(x_i+j)=y_k+(-2x_i-j)\), 所以往老鼠堆中插入元素\(-2x_i-j\).
若當前掃到一個洞\(j\),考慮是否有老鼠要反悔,從老鼠堆中取出一個元素\(i\), 若反悔的代價\(y_j+i>0\)那么讓這個老鼠堆中的老鼠反悔,同時往洞堆中加入元素\(-2y_j-i\),表示后面的老鼠再匹配這個洞的代價;否則只往洞堆中插入元素\(-y_j\).
時間復雜度\(O(n\log n)\).
Subtask 5
洞有額外權值\(w\),怎么辦呢?
考慮這樣和原來有什么區別: 原來只有老鼠會反悔(因為不會有舍近求遠故意匹配較遠洞的情況),但是現在可能出現了!
解決辦法:讓洞和洞匹配,洞也可以反悔。
插入洞\(i\)時,除了往洞堆中插入元素之外,往老鼠堆也插入元素,表示該洞反悔的代價。插入的元素是\(-y_i-w_i\), 相當于用新洞\(j\)替代該洞會少這么多的代價再加上\(y_j+w_j\).
坑:WC課件中那個“老鼠和洞同時反悔不優”目前沒看明白。
Subtask 6
洞有容量。
有一個神仙做法,見WC課件或UOJ題解。
正常做法: 往堆里存一個pair, 記錄價值和剩余容量。
每次增廣會使一個往左走的老鼠改為往右走,因此復雜度正確。
Subtask 7
洞有容量,也有附加權值,顯然Subtask 6的做法時間復雜度錯誤。
但是我們發現,如果兩個老鼠\(i,j\)分別匹配洞\(k\)且\(x_i<x_j<y_k\), 那么這兩種情況下往洞堆中丟的元素是一樣的,都是\(-y_k-w_k\).
所以我們可以“批量加入、批量增廣”,這樣復雜度就得到了保證。
其實這個東西就已經非常像費用流增廣的過程了。
費用流的一條重要性質是: 如果不是每次選全局最短路,而是按另一順序對和源點相連的邊必須連的點進行增廣,那么也是正確的。
建議結合代碼以獲取更好理解。
代碼
代碼寫出來發現和費用流神相似!
#include<cstdio> #include<cstdlib> #include<iostream> #include<queue> #include<algorithm> #define llong long long using namespace std;const int N = 1e5; const llong INF = 1000000000000ll; struct Node {int w; llong c;Node() {}Node(int _w,llong _c) {w = _w,c = _c;}bool operator <(const Node &arg) const {return c>arg.c;} }; struct Element {llong x,w,c; } a[N+3],b[N+3]; priority_queue<Node> mouse,hole; int n,m; llong ans;void insertmouse(int x) {llong ret = INF;if(!hole.empty()){Node tmp = hole.top();ret = tmp.c+x;hole.pop(); if(tmp.w-1>0) {hole.push(Node(tmp.w-1,tmp.c));}}ans += ret;mouse.push(Node(1,-x-ret)); }void inserthole(int x,int w,llong c) {int rst = w;while(!mouse.empty() && rst>0){Node tmp = mouse.top();llong val = x+c+tmp.c;if(val>=0) break;int flow = min(rst,tmp.w);ans += val*flow; rst -= flow;hole.push(Node(flow,-val-x+c));mouse.pop(); if(tmp.w-flow>0) {mouse.push(Node(tmp.w-flow,tmp.c));}}if(rst>0) {hole.push(Node(rst,-x+c));}if(w-rst>0) {mouse.push(Node(w-rst,-x-c));} }int main() {scanf("%d%d",&n,&m); llong sum = 0ll;for(int i=1; i<=n; i++) {scanf("%lld",&a[i].x);}for(int i=1; i<=m; i++) {scanf("%lld%lld%lld",&b[i].x,&b[i].c,&b[i].w); sum += b[i].w;}if(sum<n) {printf("-1"); return 0;}int i = 1,j = 1;while(i<=n||j<=m){if(i<=n && (j>m||a[i].x<b[j].x)) {insertmouse(a[i].x); i++;}else {inserthole(b[j].x,b[j].w,b[j].c); j++;}}printf("%lld\n",ans);return 0; }總結
以上是生活随笔為你收集整理的UOJ #455 [UER #8]雪灾与外卖 (贪心、模拟费用流)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Codeforces 482E ELCA
- 下一篇: BZOJ 3836 Codeforces