首页 > 代码库 > 【bzoj4200】[Noi2015]小园丁与老司机 STL-map+dp+有上下界最小流
【bzoj4200】[Noi2015]小园丁与老司机 STL-map+dp+有上下界最小流
题目描述
输入
输入文件的第 1 行包含 1 个正整数 n,表示许愿树的数量。
输出
样例输入
6
-1 1
1 1
-2 2
0 8
0 9
0 10
样例输出
3
2 1 3
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 50010using 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+有上下界最小流