4200: [Noi2015]小园丁与老司机

57 篇文章 0 订阅

4200: [Noi2015]小园丁与老司机

Time Limit: 20 Sec   Memory Limit: 512 MBSec   Special Judge
Submit: 139   Solved: 80
[ Submit][ Status][ Discuss]

Description

小园丁 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 最少需要租用多少台轧路机。

Input

 输入文件的第 1 行包含 1 个正整数 n,表示许愿树的数量。

接下来 n 行,第 i+1 行包含 2个整数 xi,yi,中间用单个空格隔开,表示第 i 棵许愿树的坐标。

Output

输出文件包括 3 行。
输出文件的第 1 行输出 1 个整数 m,表示 Mr. P 最多能在多少棵树下许愿。
输出文件的第 2 行输出 m 个整数,相邻整数之间用单个空格隔开,表示 Mr. P 应该依次在哪些树下许愿。
输出文件的第 3 行输出 1 个整数,表示 Mr. S 最少需要租用多少台轧路机。

Sample Input

6
-1 1
1 1
-2 2
0 8
0 9
0 10

Sample Output

3
2 1 3
3

explanation

最优路线 2 条可许愿 3 次:(0,0)→(1,1)→(−1,1)→(−2,2)(0,0)→(1,1)→(−1,1)→(−2,2) 或 (0,0)→(0,8)→(0,9)→(0,10)(0,0)→(0,8)→(0,9)→(0,10)。 至少 3 台轧路机,路线是 (0,0)→(1,1)(0,0)→(1,1),(−1,1)→(−2,2)(−1,1)→(−2,2) 和 (0,0)→(0,8)→(0,9)→(0,10)(0,0)→(0,8)→(0,9)→(0,10)。

HINT


Source

[ Submit][ Status][ Discuss]




dp + 有上下界的最小流,细节非常非常非常多。。。。。搞了整整一上午。。。

先考虑前两问,,老司机的开车方向只能向纵坐标增大,或者是左右开,因而可以分开dp

定义g[i]:第i个点,由y小于yi的点转移而来,最大值

那么对于每个i,从下往上走到i的方向只有三个,找出这三个方向上距离i最近的点,直接转移就行了

定义f[i]:第i个点,由左边或者右边开过来的最大值

对于f[i]的转移,首先让所有可能的路径开到当前这一层,也就是处理好该层所有的g[i]

贪心地想,如果要从同层的i开到j,假设i在j左边,那么最好的方案,就是i先开到最左端,然后慢慢往右开

反过来也是一样的,因此对于每层的f[i],从左往右从右往左分别用g[i]更新一次就行了

因而对于g[i]的转移,其实是要同时考虑三个方向上的点的f[i]和g[i]

最后检查一下所有点的f[i],g[i],就能得出第一问了。

现在要找出路径,任取一个合法的终点,每次判断它是属于哪一类(同层转移或者下层往上得到)

然后在符合的转移集合直接枚举上一个点,再继续搜索,要注意同层转移的时候把中间点加入

这样就解决了前两问,对于第三问,建模如下

倒着dp一次,和找路径的方式一样,处理出所有可能成为最短路径上的边,同层边忽略

对于从下往上的边(i,j),从i向j连一条下界为1,上界为INF的边

对于所有可能在最短路上的点,从s到它连一条INF的边,它到t连一条INF的边

也就是说,可以无限从这个点进入,无限在这个点停止

对于该网络的最小可行流就是第三问的答案

怎么求最小流?在网上搜了很久很久,,确定了时间复杂度低又好实现的模板

先构造成循环流,但先不添加(t,s,INF)这条边

然后跑一遍超级源汇最大流,再添加(t,s,INF),跑第二遍超级源汇最大流,这次跑出的最大流就是原图最小流了


最后,,此题细节很多。。

网上看到有人实现的时候,说题目给定每层的点不超过1k,因而每层直接O(N^2)暴力找路

然后又说只T第7个点。其实第7个点根本没有每层不超过1k点的限制的

因此,我处理的时候,是开一个vis数组,判断哪些点有没有被加入最短路集合

这样dp就可以写成BFS的模式,但是不能同时找随机路径(因为路径的点可能在入集合前就被经过了)


总之码农题都是好麻烦的。。。。

#include<iostream>
#include<cstdio>
#include<cstring>
#include<vector>
#include<queue>
#include<algorithm>
#include<cmath>
#include<map>
using namespace std;

const int maxn = 5E4 + 50;
const int maxm = 2E6 + 20;
const int INF = ~0U>>1;

struct E{
	int to,cap,flow; E(){}
	E(int to,int cap,int flow): to(to),cap(cap),flow(flow){}
}edgs[maxm];

struct P{
	int x,y; P(){}
	P(int x,int y): x(x),y(y){}
}Point[maxn];

struct data{
	int Num,typ; data(){}
	data(int Num,int typ): Num(Num),typ(typ){}
};

struct d2{
	int x,y; d2(){}
	d2(int x,int y): x(x),y(y){}
	bool operator < (const d2 &B) const 
	{
		if (x < B.x) return 1;
		if (x > B.x) return 0;
		return y < B.y;
	}
};

int n,cur = 1,cnt,tp,s,t,S,T,sum,Ans,a[maxn],in[maxn],out[maxn]
	,Cur[maxn],f[maxn],g[maxn],pos[maxn],Road[maxn],L[maxn];
bool in_road[maxn],vis[maxn][2],Reach[maxn][2],Typ[maxn];

vector <int> v[maxn],p[maxn],G[maxn];
queue <int> Q;
queue <data> Q2;
map <int,int> ma,mb,mc;
map <d2,bool> Edgs;

bool cmp(const int &x,const int &y) {return Point[x].x < Point[y].x;}
void Add(int x,int y,int cap)
{
	v[x].push_back(cnt); edgs[cnt++] = E(y,cap,0);
	v[y].push_back(cnt); edgs[cnt++] = E(x,0,0);
}
int Tree(const int &x,const int &y,const int &siz)
{
	return x < y?y:siz - 1 - y;
}

int getint()
{
	char ch = getchar(); int ret = 0,A = 1;
	while (ch < '0' || '9' < ch)
	{
		if (ch == '-') A = -1;
		ch = getchar();
	}
	while ('0' <= ch && ch <= '9')
		ret = ret*10 + ch - '0',ch = getchar();
	return ret*A;
}

void Pre_Work()
{
	n = getint();
	for (int i = 1; i <= n; i++)
	{
		int x = getint(),y = getint();
		Point[i] = P(x,y); a[i] = y;
	}
	Point[++n] = P(0,0); sort(a + 1,a + n + 1);
	for (int i = 2; i <= n; i++)
		if (a[i] != a[i-1]) a[++cur] = a[i];
	for (int i = 1; i <= n; i++)
	{
		int pos = lower_bound(a + 1,a + cur + 1,Point[i].y) - a;
		p[pos].push_back(i);
	}
}

void Dp()
{
	ma[0] = n; mb[0] = n; mc[0] = n; Reach[n][0] = Reach[n][1] = 1;
	for (int i = 2; i <= cur; i++)
	{
		int siz = p[i].size(); sort(p[i].begin(),p[i].end(),cmp);
		for (int j = 0; j < siz; j++)
		{
			int now = p[i][j]; P o = Point[now]; pos[now] = j;
			if (ma.count(o.x))
			{
				int Pre = ma[o.x]; int w = max(f[Pre],g[Pre]);
				g[now] = max(g[now],w + 1);
				G[now].push_back(Pre); Reach[now][1] = 1;
			}
			if (mb.count(o.x - o.y))
			{
				int Pre = mb[o.x-o.y]; int w = max(f[Pre],g[Pre]);
				g[now] = max(g[now],w + 1);
				G[now].push_back(Pre); Reach[now][1] = 1;
			}
			if (mc.count(o.x + o.y))
			{
				int Pre = mc[o.x+o.y]; int w = max(f[Pre],g[Pre]);
				g[now] = max(g[now],w + 1);
				G[now].push_back(Pre); Reach[now][1] = 1;
			}
				
		}
		int Max = Reach[p[i][0]][1]?g[p[i][0]]:-INF;
		for (int j = 1; j < siz; j++)
		{
			int now = p[i][j]; 
			if (Max != -INF) 
				f[now] = Max + 1,Reach[now][0] = 1;
			if (Reach[now][1])
				Max = max(Max + 1,g[now] + j);
			else if (Max != -INF) ++Max;
		}
		Max = Reach[p[i][siz-1]][1]?g[p[i][siz-1]]:-INF;
		for (int j = siz - 2; j >= 0; j--)
		{
			int now = p[i][j]; 
			if (Max != -INF) 
				f[now] = max(f[now],Max + 1),Reach[now][0] = 1;
			if (Reach[now][1])
				Max = max(Max + 1,g[now] + siz - 1 - j);
			else if (Max != -INF) ++Max;
		}
		for (int j = 0; j < siz; j++)
		{
			int now = p[i][j]; P o = Point[now];
			if (Reach[now][0] || Reach[now][1])
				ma[o.x] = mb[o.x-o.y] = mc[o.x+o.y] = now;
		}
	}
}

void Push_Road(int i,int k,int siz,int Y)
{
	if (i < pos[k])
	{
		for (int j = pos[k] - 1; j > i; j--)
			Road[++tp] = p[Y][j];
		for (int j = 0; j <= i; j++)
			Road[++tp] = p[Y][j];
		Typ[tp] = 1;
	}
	else
	{
		for (int j = pos[k] + 1; j < i; j++)
			Road[++tp] = p[Y][j];
		for (int j = siz - 1; j >= i; j--)
			Road[++tp] = p[Y][j];
		Typ[tp] = 1;
	}
}

void Dfs_Road(int x,int typ)
{
	Road[++tp] = x;
	if (typ)
	{
		for (int i = 0; i < G[x].size(); i++)
		{
			int to = G[x][i]; bool flag = 0,Nex_typ;
			if (Reach[to][0] && f[to] + 1 == g[x])
				flag = 1,Nex_typ = 0;
			if (Reach[to][1] && g[to] + 1 == g[x])
				flag = 1,Nex_typ = 1; 
			if (flag)
			{
				Dfs_Road(to,Nex_typ); return;
			}
		}
		
	}
	else
	{
		int Y = lower_bound(a + 1,a + cur + 1,Point[x].y) - a,siz = p[Y].size();
		for (int i = 0; i < siz; i++)
			if (i != pos[x])
			{
				int to = p[Y][i];
				if (Reach[to][1] && g[to] + Tree(i,pos[x],siz) == f[x])
				{
					Push_Road(i,x,siz,Y),--tp;
					Dfs_Road(Road[tp+1],1); return;
				}
			}
	}
}

void Build_Graph()
{
	for (int i = 1; i <= n; i++)
	{
		if (f[i] == Ans)
		{
			vis[i][0] = 1; Q2.push(data(i,0));
			if (!tp) Dfs_Road(i,0);
		}
		if (g[i] == Ans)
		{
			vis[i][1] = 1; Q2.push(data(i,1));
			if (!tp) Dfs_Road(i,1);
		}
	}
	while (!Q2.empty())
	{
		int k = Q2.front().Num,typ = Q2.front().typ; Q2.pop(); in_road[k] = 1;
		if (typ)
		{
			for (int i = 0; i < G[k].size(); i++)
			{
				int to = G[k][i]; bool flag = 0;
				if (Reach[to][0] && f[to] + 1 == g[k])
				{
					if (!vis[to][0]) vis[to][0] = 1,Q2.push(data(to,0)); flag = 1;
				}
				if (Reach[to][1] && g[to] + 1 == g[k])
				{
					if (!vis[to][1]) vis[to][1] = 1,Q2.push(data(to,1)); flag = 1; 
				}
				if (flag && !Edgs.count(d2(to,k))) 
					++in[k],++out[to],Add(to,k,INF),Edgs[d2(to,k)] = 1;
			}
		}
		else
		{
			int Y = lower_bound(a + 1,a + cur + 1,Point[k].y) - a;
			int siz = p[Y].size();
			for (int i = 0; i < siz; i++)
				if (i != pos[k])
				{
					int to = p[Y][i];
					if (Reach[to][1] && g[to] + Tree(i,pos[k],siz) == f[k])
					{
						if (!vis[to][1]) vis[to][1] = 1,Q2.push(data(to,1));
					}
				}
		}
	}
	for (int i = 1; i <= n; i++)
		if (in_road[i])
		{
			Add(s,i,INF); Add(i,t,INF);
			int du = in[i] - out[i];
			if (du > 0) Add(S,i,du),sum += du;
			else if (du < 0) Add(i,T,-du);
		}
}

int Dfs(int x,int A)
{
	if (x == T) return A; int flow = 0;
	for (int &i = Cur[x]; i < v[x].size(); i++)
	{
		E &e = edgs[v[x][i]];
		if (e.cap == e.flow || L[e.to] != L[x] + 1) continue;
		int f = Dfs(e.to,min(A,e.cap - e.flow));
		if (!f) continue; flow += f; e.flow += f;
		edgs[v[x][i]^1].flow -= f; A -= f;
		if (!A) return flow;
	}
	if (!flow) L[x] = -1; return flow;
}

bool BFS()
{
	for (int i = 1; i <= T; i++) L[i] = 0;
	L[S] = 1; Q.push(S);
	while (!Q.empty())
	{
		int k = Q.front(); Q.pop();
		for (int i = 0; i < v[k].size(); i++)
		{
			E e = edgs[v[k][i]];
			if (e.cap == e.flow || L[e.to]) continue;
			L[e.to] = L[k] + 1; Q.push(e.to);
		}
	}
	return L[T];
}

int Dinic()
{
	int MaxFlow = 0;
	while (BFS())
	{
		for (int i = 1; i <= T; i++) Cur[i] = 0;
		MaxFlow += Dfs(S,INF);
	}
	return MaxFlow;
}

int main()
{
	
	Pre_Work(); Dp();
	for (int i = 1; i <= n; i++) Ans = max(Ans,max(f[i],g[i]));
	cout << Ans << endl; s = n + 1; t = s + 1; S = t + 1; T = S + 1;
	Build_Graph();
	for (int i = tp - 1; i > 1; i--) 
		printf("%d ",Road[i]); cout << Road[1] << endl;
	//if (tp - 1 != Ans) puts("Wrong Answer!"); else puts("Accepted");
	Dinic(); Add(t,s,INF); cout << Dinic();
	return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值