【bzoj4200】[Noi2015]小園丁與老司機 STL-map+dp+有上下界最小流
題目描述
小園丁 Mr. S 負責看管一片田野,田野可以看作一個二維平面。田野上有 nn 棵許願樹,編號 1,2,3,…,n1,2,3,…,n,每棵樹可以看作平面上的一個點,其中第 ii 棵樹 (1≤i≤n1≤i≤n) 位於坐標 (xi,yi)(xi,yi)。任意兩棵樹的坐標均不相同。 老司機 Mr. P 從原點 (0,0)(0,0) 駕車出發,進行若幹輪行動。每一輪,Mr. P 首先選擇任意一個滿足以下條件的方向: 為左、右、上、左上 45°45° 、右上 45°45° 五個方向之一。 沿此方向前進可以到達一棵他尚未許願過的樹。 完成選擇後,Mr. P 沿該方向直線前進,必須到達該方向上距離最近的尚未許願的樹,在樹下許願並繼續下一輪行動。如果沒有滿足條件的方向可供選擇,則停止行動。他會采取最優策略,在盡可能多的樹下許願。若最優策略不唯一,可以選擇任意一種。 不幸的是,小園丁 Mr. S 發現由於田野土質松軟,老司機 Mr. P 的小汽車在每輪行進過程中,都會在田野上留下一條車轍印,一條車轍印可看作以兩棵樹(或原點和一棵樹)為端點的一條線段。 在 Mr. P 之後,還有很多許願者計劃駕車來田野許願,這些許願者都會像 Mr. P 一樣任選一種最優策略行動。Mr. S 認為非左右方向(即上、左上 45°45° 、右上 45°45° 三個方向)的車轍印很不美觀,為了維護田野的形象,他打算租用一些軋路機,在這群許願者到來之前夯實所有“可能留下非左右方向車轍印”的地面。 “可能留下非左右方向車轍印”的地面應當是田野上的若幹條線段,其中每條線段都包含在某一種最優策略的行進路線中。每臺軋路機都采取滿足以下三個條件的工作模式: 從原點或任意一棵樹出發。 只能向上、左上 45°45° 、右上 45°45° 三個方向之一移動,並且只能在樹下改變方向或停止。 只能經過“可能留下非左右方向車轍印”的地面,但是同一塊地面可以被多臺軋路機經過。 現在 Mr. P 和 Mr. S 分別向你提出了一個問題: 請給 Mr .P 指出任意一條最優路線。 請告訴 Mr. S 最少需要租用多少臺軋路機。輸入
輸入文件的第 1 行包含 1 個正整數 n,表示許願樹的數量。
接下來 n 行,第 i+1 行包含 2個整數 xi,yi,中間用單個空格隔開,表示第 i 棵許願樹的坐標。輸出
輸出文件包括 3 行。 輸出文件的第 1 行輸出 1 個整數 m,表示 Mr. P 最多能在多少棵樹下許願。 輸出文件的第 2 行輸出 m 個整數,相鄰整數之間用單個空格隔開,表示 Mr. P 應該依次在哪些樹下許願。 輸出文件的第 3 行輸出 1 個整數,表示 Mr. S 最少需要租用多少臺軋路機。樣例輸入
6
-1 1
1 1
-2 2
0 8
0 9
0 10
樣例輸出
3
2 1 3
題解
STL-map+dp+網絡流最小流
碼農題!碼農題!碼農題!
先處理第一問和第二問。
考慮到車子只能向上或向左右方向走,不能向下走,所以先將所有樹的坐標按照y從小到大排序,y相同則按x從小到大排序。
然後如果只考慮向上轉移,那麽顯然是一個dp。開3個map存儲y、x+y、x-y為某值的最後一個點是哪個點,然後轉移一下並記錄路徑就好了。
但是加上向左右轉移後情況就變得復雜許多。
我們把同一行的點拿出來,如果用a更新b,只有兩種情況:a在b左邊、a在b右邊。a在b左邊時,一定是先經過a及a左邊的點,再經過a、b中間的點及b,相當於經過了b左邊的所有點。所以維護一個f[a]的前綴最大值即可。右邊同理。註意記錄路徑的方式要區分開。
然後找出f的最大值即可解決第一問,根據記錄的路徑即可解決第二問。註意同行轉移的路徑情況。
第三問顯然是個最小流,但是要先把圖建出來,即找到什麽樣的邊可能為“答案邊”。
這時想到了“什麽樣的邊在最短路上”的解決方法:以起點和終點分別求最短路,判斷某條邊連接的兩點分別到起點和終點的距離之和是否等於最短路。
那麽這道題與上面是類似的,我們可以倒過來再做一次dp,求出某個點開始到答案點最多能夠經過多少棵樹。
把f值等於答案的點dp初始值設為1,其余為-inf,上下更新和正著dp一樣。
左右更新稍有區別,如果用a更新b,那麽正著時是用b更新a,一定是先到b遠離a一側的所有點,再到a。
所以維護的是g[i]+i或g[i]-i的最大值。
dp完之後,剩下的就交給最小流。
對於某條非水平邊,如果它可能為“答案邊”,就在兩點之間連一條容量下界為1,上界為inf的邊。
然後S向每個點、每個點向T連容量為inf的邊,這張圖的最小流即為答案。
但是按照正常的最小流建圖方法:T向S連邊、設立SS和TT,分別向入度>0和<0的點連邊,這樣做會TLE。
於是才知道本題有個高端的建圖方法:S向入度>0的點連邊,T向入度<0的點連邊,跑最大流,滿流-最大流即為答案。
自己想了一下:可以這樣理解:正常的建圖中第一次是一定滿流的,不妨讓第一次的所有流量都經過T->S這條邊,那麽刪除SS、TT、T->S邊後新得到的圖中所有與T相連的邊都是指向入度>0的點,且容量為入度;所有連向S的邊都是從入度<0的點連出來的,且容量為入度的相反數。於是我們可以直接進行這個第二個過程,即可得到最小流。
代碼6K~
#include <cstdio> #include <cstring> #include <algorithm> #include <queue> #include <map> #define N 50010 using namespace std; const int inf = 1 << 30; struct data { int x , y , id; }a[N]; queue<int> q; int n , f[N] , mx[N] , last[N] , pre[N] , sta[N] , top , ans , g[N] , ind[N] , flow; int head[N] , to[N * 10] , val[N * 10] , next[N * 10] , cnt = 1 , s , t , dis[N]; bool cmp(data a , data b) { return a.y == b.y ? a.x < b.x : a.y < b.y; } void output() { int i , j; for(i = ans ; i ; i = last[i]) { if(!pre[i]) sta[++top] = i; else { if(pre[i] < i) { for(j = i ; j > pre[i] ; j -- ) sta[++top] = j; for(j = pre[i] ; j && a[j].y == a[i].y ; j -- ); for(j ++ ; j <= pre[i] ; j ++ ) sta[++top] = j; } else { for(j = i ; j < pre[i] ; j ++ ) sta[++top] = j; for(j = pre[i] ; j <= n && a[j].y == a[i].y ; j ++ ); for(j -- ; j >= pre[i] ; j -- ) sta[++top] = j; } i = pre[i]; } } for(i = top ; i ; i -- ) printf("%d " , a[sta[i]].id); printf("\n"); } void dp1() { memset(f , 0xc0 , sizeof(f)); map<int , int> p1 , p2 , p3; int l , r , i , pos; p1[0] = p2[0] = p3[0] = f[0] = 0; for(l = r = 1 ; l <= n ; l = r + 1) { while(r < n && a[r + 1].y == a[l].y) r ++ ; for(i = l ; i <= r ; i ++ ) { if(p1.find(a[i].x) != p1.end()) { pos = p1[a[i].x]; if(f[i] < f[pos] + 1) f[i] = f[pos] + 1 , last[i] = pos; } if(p2.find(a[i].x + a[i].y) != p2.end()) { pos = p2[a[i].x + a[i].y]; if(f[i] < f[pos] + 1) f[i] = f[pos] + 1 , last[i] = pos; } if(p3.find(a[i].x - a[i].y) != p3.end()) { pos = p3[a[i].x - a[i].y]; if(f[i] < f[pos] + 1) f[i] = f[pos] + 1 , last[i] = pos; } p1[a[i].x] = p2[a[i].x + a[i].y] = p3[a[i].x - a[i].y] = i; } for(i = l ; i <= r ; i ++ ) mx[i] = f[i]; for(i = l + 1 , pos = l ; i <= r ; i ++ ) { if(f[pos] + i - l > mx[i]) mx[i] = f[pos] + i - l , pre[i] = pos; if(f[i] > f[pos]) pos = i; } for(i = r - 1 , pos = r ; i >= l ; i -- ) { if(f[pos] + r - i > mx[i]) mx[i] = f[pos] + r - i , pre[i] = pos; if(f[i] > f[pos]) pos = i; } for(i = l ; i <= r ; i ++ ) f[i] = mx[i]; } for(i = 1 ; i <= n ; i ++ ) if(f[i] > f[ans]) ans = i; printf("%d\n" , f[ans]); output(); } void dp2() { memset(g , 0xc0 , sizeof(g)); map<int , int> p1 , p2 , p3; int l , r , i , pos; for(i = 1 ; i <= n ; i ++ ) if(f[i] == f[ans]) g[i] = 1; for(l = r = n ; r ; r = l - 1) { while(l > 1 && a[l - 1].y == a[r].y) l -- ; for(i = l ; i <= r ; i ++ ) { if(p1.find(a[i].x) != p1.end()) g[i] = max(g[i] , g[p1[a[i].x]] + 1); if(p2.find(a[i].x + a[i].y) != p2.end()) g[i] = max(g[i] , g[p2[a[i].x + a[i].y]] + 1); if(p3.find(a[i].x - a[i].y) != p3.end()) g[i] = max(g[i] , g[p3[a[i].x - a[i].y]] + 1); p1[a[i].x] = p2[a[i].x + a[i].y] = p3[a[i].x - a[i].y] = i; } for(i = l ; i <= r ; i ++ ) mx[i] = g[i]; for(i = l + 1 , pos = l ; i <= r ; i ++ ) { mx[i] = max(mx[i] , g[pos] + r - pos); if(g[i] - i > g[pos] - pos) pos = i; } for(i = r - 1 , pos = r ; i >= l ; i -- ) { mx[i] = max(mx[i] , g[pos] + pos - l); if(g[i] + i > g[pos] + pos) pos = i; } for(i = l ; i <= r ; i ++ ) g[i] = mx[i]; } } void add(int x , int y , int z) { to[++cnt] = y , val[cnt] = z , next[cnt] = head[x] , head[x] = cnt; to[++cnt] = x , val[cnt] = 0 , next[cnt] = head[y] , head[y] = cnt; } void build() { map<int , int> p1 , p2 , p3; int i , pos; p1[0] = p2[0] = p3[0] = 0; s = n + 1 , t = n + 2; for(i = 1 ; i <= n ; i ++ ) { if(p1.find(a[i].x) != p1.end()) { pos = p1[a[i].x]; if(f[pos] + g[i] == f[ans]) add(pos , i , inf) , ind[pos] -- , ind[i] ++ ; } if(p2.find(a[i].x + a[i].y) != p2.end()) { pos = p2[a[i].x + a[i].y]; if(f[pos] + g[i] == f[ans]) add(pos , i , inf) , ind[pos] -- , ind[i] ++ ; } if(p3.find(a[i].x - a[i].y) != p3.end()) { pos = p3[a[i].x - a[i].y]; if(f[pos] + g[i] == f[ans]) add(pos , i , inf) , ind[pos] -- , ind[i] ++ ; } p1[a[i].x] = p2[a[i].x + a[i].y] = p3[a[i].x - a[i].y] = i; } for(i = 0 ; i <= n ; i ++ ) { if(ind[i] > 0) add(s , i , ind[i]) , flow += ind[i]; if(ind[i] < 0) add(i , t , -ind[i]); } } bool bfs() { int x , i; memset(dis , 0 , sizeof(dis)); while(!q.empty()) q.pop(); dis[s] = 1 , q.push(s); while(!q.empty()) { x = q.front() , q.pop(); for(i = head[x] ; i ; i = next[i]) { if(val[i] && !dis[to[i]]) { dis[to[i]] = dis[x] + 1; if(to[i] == t) return 1; q.push(to[i]); } } } return 0; } int dinic(int x , int low) { if(x == t) return low; int temp = low , i , k; for(i = head[x] ; i ; i = next[i]) { if(val[i] && dis[to[i]] == dis[x] + 1) { k = dinic(to[i] , min(temp , val[i])); if(!k) dis[to[i]] = 0; val[i] -= k , val[i ^ 1] += k; if(!(temp -= k)) break; } } return low - temp; } int main() { int i; scanf("%d" , &n); for(i = 1 ; i <= n ; i ++ ) scanf("%d%d" , &a[i].x , &a[i].y) , a[i].id = i; sort(a + 1 , a + n + 1 , cmp); dp1(); dp2(); build(); while(bfs()) flow -= dinic(s , inf); printf("%d\n" , flow); return 0; }
【bzoj4200】[Noi2015]小園丁與老司機 STL-map+dp+有上下界最小流