题目描述

小园丁 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
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+有上下界最小流的更多相关文章

  1. [BZOJ4200][Noi2015]小园丁与老司机

    4200: [Noi2015]小园丁与老司机 Time Limit: 20 Sec  Memory Limit: 512 MBSec  Special JudgeSubmit: 106  Solved ...

  2. BZOJ4200 NOI2015小园丁与老司机(动态规划+上下界网络流)

    一看上去就是一个二合一的题.那么先解决第一部分求最优路线(及所有可能在最优路线上的线段). 由于不能往下走,可以以y坐标作为阶段.对于y坐标不同的点,我们将可以直接到达的两点连边,显然这样的边的个数是 ...

  3. UOJ#132&bzoj4200[Noi2015]小园丁与老司机

    看,这是一个传送门 Part A 把坐标离散化,按照纵坐标为第一关键字,横坐标为第二关键字排序 以$f_i$记录来到$i$这个点最多经过点数,那么答案显而易见就是$f_i$加上该层点数 转移的话就是分 ...

  4. bzoj4200: [Noi2015]小园丁与老司机(可行流+dp)

    传送门 这该死的码农题…… 题解在这儿->这里 //minamoto #include<iostream> #include<cstdio> #include<cs ...

  5. [UOJ#132][BZOJ4200][luogu_P2304][NOI2015]小园丁与老司机

    [UOJ#132][BZOJ4200][luogu_P2304][NOI2015]小园丁与老司机 试题描述 小园丁 Mr. S 负责看管一片田野,田野可以看作一个二维平面.田野上有 \(n\) 棵许愿 ...

  6. 【BZOJ4200】[Noi2015]小园丁与老司机 DP+最小流

    [BZOJ2839][Noi2015]小园丁与老司机 Description 小园丁 Mr. S 负责看管一片田野,田野可以看作一个二维平面.田野上有 nn 棵许愿树,编号 1,2,3,…,n1,2, ...

  7. uoj132/BZOJ4200/洛谷P2304 [Noi2015]小园丁与老司机 【dp + 带上下界网络流】

    题目链接 uoj132 题解 真是一道大码题,,,肝了一个上午 老司机的部分是一个\(dp\),观察点是按\(y\)分层的,而且按每层点的上限来看可以使用\(O(nd)\)的\(dp\),其中\(d\ ...

  8. [Noi2015]小园丁和老司机

    来自FallDream的博客,未经允许,请勿转载,谢谢. 小园丁 Mr. S 负责看管一片田野,田野可以看作一个二维平面.田野上有n棵许愿树,编号1,2,3,…,n,每棵树可以看作平面上的一个点,其中 ...

  9. luogu P2304 [NOI2015]小园丁与老司机 dp 上下界网络流

    LINK:小园丁与老司机 苦心人 天不负 卧薪尝胆 三千越甲可吞吴 AC的刹那 真的是泪目啊 很久以前就写了 当时记得特别清楚 写到肚子疼.. 调到胳膊疼.. ex到根不不想看的程度. 当时wa了 一 ...

  10. bzoj 4200: [Noi2015]小园丁与老司机【dp+有上下界最小流】

    洛谷上有个点死活卡不过去,不知道是哪里写丑了orz 参考:https://www.cnblogs.com/ditoly/p/BZOJ4200.html 从上往下dp,设f为不向左右走直接上去的值,g为 ...

随机推荐

  1. Obj-C Memory Management

    Memory management is one of the most important process in any programming language. It is the proces ...

  2. Gitlab User Guide

    Installation Configuration Set user name and email Add SSH keys Repository Create New Repository Clo ...

  3. 2017“编程之美”终章:AI之战勇者为王

    编者按:8月15日,第六届微软“编程之美”挑战赛在选手的火热比拼中圆满落下帷幕.“编程之美”挑战赛是由微软主办,面向高校学生开展的大型编程比赛.自2012年起,微软每年都在革新比赛命题.紧跟时代潮流, ...

  4. 如何诊断 11.2 集群节点驱逐问题 (文档 ID 1674872.1)

    适用于: Oracle Database - Enterprise Edition - 版本 11.2.0.1 到 11.2.0.2 [发行版 11.2]本文档所含信息适用于所有平台 用途 这篇文档提 ...

  5. select a.no,a.name,b.subid,b.subname,c.score

    select a.no,a.name,b.subid,b.subname,c.score from a,b,c  where a.no = c.no and b.subid = c.subid ;

  6. python基础一 day14 复习

    迭代器和生成器迭代器:双下方法 : 很少直接调用的方法.一般情况下,是通过其他语法触发的可迭代的 —— 可迭代协议 含有__iter__的方法('__iter__' in dir(数据))可迭代的一定 ...

  7. Asp.Net Core 入门(八)—— Taghelper

    Taghelper是一个服务端的组件,可以在Razor文件中创建和渲染HTML元素,类似于我们在Asp.Net MVC中使用的Html Taghelper.Asp.Net Core MVC内置的Tag ...

  8. Ubuntu下Hyperledger Fabric v0.6安装部署

    系统环境:虚拟机VMware Workstation中的Ubuntu 16.04LTS 1.环境准备 1.1安装Docker Docker安装命令: curl –fsSL https://get.do ...

  9. CS193p Lecture 4 - Foundation, Attributed Strings

    消息机制 调用一个实例(instance)的方法(method),就是向该实例的指针发送消息(message),实例收到消息后,从自身的实现(implementation)中寻找响应这条消息的方法. ...

  10. cocos2d popSceneWithTransition()方法

    要在CCDirector.h中增加如下方法: template <typename T> void popSceneWithTransition(float t) { CCASSERT(_ ...