bzoj4200[Noi2015]小园丁与老司机 STL-map+dp+有上下界最小流

Posted GXZlegend

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了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;
}

 

以上是关于bzoj4200[Noi2015]小园丁与老司机 STL-map+dp+有上下界最小流的主要内容,如果未能解决你的问题,请参考以下文章

bzoj4200[Noi2015]小园丁与老司机 STL-map+dp+有上下界最小流

UOJ132 NOI2015小园丁与老司机

省选之前的未完成的计划(截至到省选)

BZOJ4197: [Noi2015]寿司晚宴

BZOJ 4197 NOI 2015 寿司晚宴 状压DP

BZOJ 4196: [Noi2015]软件包管理器