题目描述

小园丁 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. Hibernate框架关系映射一对多双向关联

    直入主题,首先大配置常规配置, 这里住要说关联关系,大配置不多少,而且jar包默认添加好,笔者用的是idea2016. 然后我们知道关联关系主要是在小配置添加节点来配置属性.个人认为关联映射,就是对应 ...

  2. window服务的使用

    目前的项目中使用很多服务来进行实现.服务是依靠windows操作系统来实现.可以是定时器类型,比如定时执行费时的任务,这种任务时最多.也可以是一些服务(SOAP)的宿主,不在限制与iis,不现在限制于 ...

  3. HDU 4044 GeoDefense (树形DP,混合经典)

    题意: 给一棵n个节点的树,点1为敌方基地,叶子结点都为我方阵地.我们可以在每个结点安放炸弹,每点至多放一个,每个结点有ki种炸弹可选,且每种炸弹有一个花费和一个攻击力(1点攻击力使敌人掉1点hp). ...

  4. Servlet和JSP之自定义标签学习

      此文章会讲述简单标签处理器,因为经典自定义标签处理器没有简单标签处理器方便使用,故在此不进行描述. 参考:慕课网的<JSP自定义标签>视频; <Servlet.JSP和Sprin ...

  5. App Store中的开源游戏汇总

    这是国外达人收集的曾经在app store上出现过,或者还在app store上卖的iOS开源游戏的列表,其中代码大部分人你托管在google code或者github上,其中有很多使用Cocos2D ...

  6. poj2312Battle City BFS

    题意: M行N列矩阵, 'Y'表示开始位置, 'T'表示目标位置, 从开始位置到目标位置至少需要走多少步,其中, 'S', 'R'表示不能走, 'B' 花费为2, 'E'花费为1. 思路:纯 BFS. ...

  7. kubernetes概念

    kubernetes blog cluster cluster是计算.存储.和网络资源的集合,kubernetes利用这些资源运行各种基于容器的应用. master master是cluster的大脑 ...

  8. 初识 Hibernate

    Hibernate 框架 1.1   什么是框架? 框架是一个提供了可重用的公共结构半成品. 2.1   关于Hibernate Hibernate是数据持久层的一个轻量级框架.数据持久层的框架有很多 ...

  9. java常用流处理工具StreamTool 常见的InputStream流转字符串, 转字节数组等等

    ava 常用流处理工具 StreamTool ,常见的InputStream 流转字符串, 转字节数组等等 **应用场景: ** 1. 文件上传 2. js / css / img 等文件读取输出. ...

  10. c++ 结构体,设置物品体积并输出物品属性

    #include <iostream> using namespace std; struct box { char maker[40]; float height; float widt ...