【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
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;
}
时间: 2024-08-24 20:40:30

【bzoj4200】[Noi2015]小园丁与老司机 STL-map+dp+有上下界最小流的相关文章

NOI2015 小园丁与老司机

http://uoj.ac/problem/132 这道题前2行的输出比较容易,就是简单的动态规划,然后第3行就是比较少见的有上下界的最小流. 前2行比较容易,我们讨论一下第3行的解法吧. 比如第1个样例: 我们先找出那些可能成为最优解的非平行边: Case11~14做法: 这里保证存在一种最优解,使得轧路机不重复经过同一路面. 我们求出每个点i的入度in[i]和出度out[i]. 然后就是∑max(in[i]-out[i],0). 我们可以这样想, 当in[i]>out[i]时,必定有in[i

UOJ132 【NOI2015】小园丁与老司机

本文版权归ljh2000和博客园共有,欢迎转载,但须保留此声明,并给出原文链接,谢谢合作. 本文作者:ljh2000 作者博客:http://www.cnblogs.com/ljh2000-jump/转载请注明出处,侵权必究,保留最终解释权! 题目链接:UOJ132 正解:DP+上下界网络流 解题报告: 第一.二问是一起的,DP一遍可以解决. 具体而言,f[i]记录到达i的最优值,g[i]记录前驱结点. 按y分层,不同层之间直接转,左上右上的一条直线上的点x.y坐标的和或者差相等,map保存最后

扩展封装暴雪哈希算法(blizard hash algorithm),并与STL map进行操作性能上的比较

问题描述: 1.blizard hash algorithm 是众所周知的算法,关于它极小的碰撞概率和实现的简洁性一直为热爱技术的朋友津津乐道: 2.blizard hash algorithm 有个致命的问题就是它的实现受制于一个固定的(预先开辟的buffer)的限制,暴雪给出的是1024,也即当hash table 的填充的元素(key value pair)查过1024时,就没办法再往里面进行key value 对填充,这极大的限制了它的使用.在实现的应用,我们经常会向hash table

用c++封装一个Hash Table,并与STL map 进行操作性能上的比较

问题描述: 1.在计算机科学中,hash table 是一种常用的数据结构,它本质上是一种关联容器,它映射一个key 到value.它通过一个hash function把key映射成一个整数值,这个整数值对应存放value值的容器的下标. 2.它主要支持三种操作:插入key,value对(insert),查询(search)给定key, 删除key, value对(delete); 3.它三种操作的平均时间复杂度为O(1),最糟糕情况下的时间复杂度为O(n): 4.hash table要处理核心

泛型的Binary Search Tree的实现,并与STL map进行操作性能上的比较

问题描述: 1.binary search tree是一种排序二叉树.对于key值,当前节点的小于左孩子的大于右孩子的: 2.binary search tree不是自平衡树.所以,当插入数据不是很随机时候,性能会接近O(N),N是树中节点数目; 3.理想状态下,时间复杂度是O(lgN), N是树中节点的数目: 4.下面给出一个简单的实现,并比较其和STL map的性能,一样的操作,大约耗时为STL map 的2/3: 代码如下: #ifndef _BINARY_SEARCH_TREE_H_ #

Android老司机搬砖小技巧

作为一名Android世界的搬运工,每天搬砖已经够苦够累了,走在坑坑洼洼的道路一不小心就掉坑里了. SDK常用工具类 Android SDK中本身就拥有很多轮子,熟悉这些轮子,可以提高我们的搬砖效率. android.text.TextUtils 字符串操作常用方法:isEmpty() ,join(),split()等 if(!TextUtils.isEmpty(text)){ //do something } android.webkit.URLUtil 链接相关常用方法:isHttpUrl(

银钻娱乐客服15687949443关于小程序常见问题,看完你就是老司机

怎么开通小程序?怎么注册小程序名称呢......云指在运营的过程中,收到了很多朋友类似这样的问题反馈.今天为大家送上贴心的100个关于小程序Q&A,帮助大家把所有问题一扫而光,看完你就是老司机了. 1.Q:微信支付主体需要和小程序主体一致吗? A:必须是主体一致的 2.Q:一般微信支付开通需要多久? A:1-5个工作日 3.Q:微信支付商户填写结算账户找不到开户银行的处理方法 A:微信支付商户申请填写结算账户时如果找不到所在的银行,请选择“其他银行”后手动填写所在支行全称,例如:建设银行佛山市环

老司机太多?为何科技宅男爱“撸串”

"撸",原本是宅男是家里做某些不可言说的事儿的代名词,但随着网络文化的发酵,迅速爆红.尤其是夜市文化,演变成了"撸串文化".一个人撸串,撸的是心情:两个人撸串,撸的是默契:三个人撸串,撸的是江湖.撸串时每个小餐桌都是一个指点江山.挥斥方遒的大舞台.对于科技宅男来说,更是难得的放松机会.那么问题来了,对互联网熟稔至极,个个都是老司机的互联网"民工"们,为何独爱"撸串"? 近日58同城和京东接连被曝光要采用"996&qu

“老司机”教你如何处理PDF文件转换问题

大家常用的办公室文件格式有WORD.EXCEL.PPT.JPG.PDF等等,它们各有所长,像WORD方便文字的编辑和布局,EXCEL便于排序统计计算......而PDF的特点是美观但不易修改.在工作中我们经常会遇到需要将PDF文件与其它格式之间进行相互转化的情况.那么我们需要动手重新做吗?很显然,这是个吃力不讨好的且效率低下的选择,有经验的"老司机"这时会轻描淡写地说一声:"用转换器转一下就好." 现在市场上有很多这样的转换器,可大多数都不好用.国外的由于语言和操作