UOJ132 【NOI2015】小园丁与老司机

本文版权归ljh2000和博客园共有,欢迎转载,但须保留此声明,并给出原文链接,谢谢合作。

本文作者:ljh2000
作者博客:http://www.cnblogs.com/ljh2000-jump/
转载请注明出处,侵权必究,保留最终解释权!

题目链接:UOJ132

正解:DP+上下界网络流

解题报告:

  第一、二问是一起的,DP一遍可以解决。

  具体而言,f[i]记录到达i的最优值,g[i]记录前驱结点。

  按y分层,不同层之间直接转,左上右上的一条直线上的点x、y坐标的和或者差相等,map保存最后的值(写转入会方便一些)。

  同一层之间有一点麻烦,考虑一定是从一个点走进这一层之后,把一边的所有点遍历完之后再走向另一边,从某个点走出这一层。

  这个用前缀和后缀最大值维护一下就可以了,输出方案的时候就记录一下这个点是从同一层还是从之前的层转移过来的,就可以确定经过的点了。

  第三问的话,我们可以通过前两问,得到每个点的f,对于可能出现在最优解上的边,我们都拎出来,然后建图。

  考虑题目要求我们什么。因为每条边都至少要被经过一次,就可以理解成求下界为1上界为inf的最小流。

  就变成了有上下界的最小流问题了,这道题的建图方式是:超级源点S向所有in[i]-out[i]>0的点连容量为in[i]-out[i]的边,所有in[i]-out[i]<0的点向超级汇点T连容量为out[i]-in[i]的边,原边容量为inf。

  跑一遍最大流,最后用满流-最大流即为所求。

  

  ps:我开始是用vector存下所有的可行转移,然后调了一个晚上调不出,小点都过了,大点有的AC有的WA...

    今天早上一来机房,痛下决心,task3重写!

    换成了重新DP一遍,倒着做一遍,再正着连边,这样做就好调很多了...

    建议:不要在脑子不清醒的时候写这道题,这会让你怀疑自己长了一个假脑子...  

      如果发现自己调试不出来了,建议换一种写法或者重写一遍。祝你身体健康

//It is made by ljh2000
#include <iostream>
#include <cstdlib>
#include <cstring>
#include <cstdio>
#include <cmath>
#include <algorithm>
#include <vector>
#include <map>
using namespace std;
typedef long long LL;
const int MAXN = 50011;
const int MAXM = 300011;
const int MOD = 300007;
const int inf = (1<<29);
int n,f[MAXN],g[MAXN],from[MAXN],from2[MAXN],ans,dui[MAXN],belong[MAXN],le[MAXN],ri[MAXN],ycnt;
int ecnt,first[MAXN],in[MAXN],hcnt,S,T,deep[MAXN],tot,dp[MAXN];
bool same[MAXN],vis[MAXN];
map<int,bool>mp[MAXN];
struct node{ int x,y,id; }a[MAXN];
struct edge{ int to,next,f; }e[MAXM];
struct bian{ int x,y; }b[MAXM];
inline bool cmpy(node q,node qq){ if(q.y==qq.y) return q.x<qq.x; return q.y<qq.y; }
inline void link(int x,int y,int z){
	e[++ecnt].next=first[x]; first[x]=ecnt; e[ecnt].to=y; e[ecnt].f=z;
	e[++ecnt].next=first[y]; first[y]=ecnt; e[ecnt].to=x; e[ecnt].f=0;
}

inline int getint(){
    int w=0,q=0; char c=getchar(); while((c<‘0‘||c>‘9‘) && c!=‘-‘) c=getchar();
    if(c==‘-‘) q=1,c=getchar(); while (c>=‘0‘&&c<=‘9‘) w=w*10+c-‘0‘,c=getchar(); return q?-w:w;
}

struct Hash{//哈希表查询直线上的最近的点
	int ecnt,first[MOD+12],to[100011],next[100011],w[100011];
	inline void clear(){ ecnt=0; memset(first,0,sizeof(first)); }
	inline int query(int x){ int cc=x%MOD; cc+=MOD; cc%=MOD; for(int i=first[cc];i;i=next[i]) if(to[i]==x) return w[i];	return -1; }
	inline void insert(int x,int val) {
		int cc=x%MOD; cc+=MOD; cc%=MOD;	for(int i=first[cc];i;i=next[i]) if(to[i]==x) { w[i]=val; return ; }
		next[++ecnt]=first[cc]; first[cc]=ecnt; to[ecnt]=x; w[ecnt]=val;
	}
}t1,t2,t3;

inline void DP(){//第一问输出最优值
	int l,r,last,pos; t1.insert(0,0); t2.insert(0,0); t3.insert(0,0);
	for(int i=1;i<=n;i++) f[i]=g[i]=-inf;
	for(int i=1;i<=n;i=r+1) {
		l=r=i; while(r<n && a[r+1].y==a[l].y) r++;
		ycnt++; le[ycnt]=l; ri[ycnt]=r; for(int j=l;j<=r;j++) belong[j]=ycnt;
		//转入
		for(int j=l;j<=r;j++) {
			last=t1.query(a[j].x+a[j].y); t1.insert(a[j].x+a[j].y,j); if(last==-1) continue;
			if(f[last]+1>f[j]) { f[j]=f[last]+1; from[j]=last; }
		}
		for(int j=l;j<=r;j++) {
			last=t2.query(a[j].x-a[j].y); t2.insert(a[j].x-a[j].y,j); if(last==-1) continue;
			if(f[last]+1>f[j]) { f[j]=f[last]+1; from[j]=last; }
		}
		for(int j=l;j<=r;j++) {
			last=t3.query(a[j].x); 	t3.insert(a[j].x,j); if(last==-1) continue;
			if(f[last]+1>f[j]) { f[j]=f[last]+1; from[j]=last; }
		}

		//同层之间的转移,一定是从pos走入,然后走到边界,再往回走,从某个点走出
		pos=l;//存储最大的位置,前缀max
		for(int j=l+1;j<=r;j++) {
			if(f[j-1]>f[pos]) pos=j-1;
			if(f[pos]+j-l>g[j]) { g[j]=f[pos]+j-l; from2[j]=pos; }
		}
		pos=r;//后缀max
		for(int j=r-1;j>=l;j--) {
			if(f[j+1]>f[pos]) pos=j+1;
			if(f[pos]+r-j>g[j]) { g[j]=f[pos]+r-j; from2[j]=pos; }
		}

		for(int j=l;j<=r;j++)
			if(f[j]<g[j]) { f[j]=g[j]; same[j]=1; }//打上同层转移标记
	}
	ans=-inf; for(int i=1;i<=n;i++) ans=max(ans,f[i]);
	if(ans<0) { printf("0\n\n0"); exit(0); }
	printf("%d\n",ans);
}

inline void Print(){
	int pos=1,top=0; for(int i=1;i<=n;i++) if(f[i]>f[pos]) pos=i;
	while(pos) {//逆序输出
		if(same[pos]) {
			if(from2[pos]<pos) {
				for(int i=pos;i>from2[pos];i--) dui[++top]=i;
				for(int i=le[belong[pos]];i<=from2[pos];i++) dui[++top]=i;
			}
			else{
				for(int i=pos;i<from2[pos];i++) dui[++top]=i;
				for(int i=ri[belong[pos]];i>=from2[pos];i--) dui[++top]=i;
			}
			pos=from2[pos];
			pos=from[pos];
		}
		else {	dui[++top]=pos;	pos=from[pos]; }
	}
	for(int i=top;i>=1;i--) printf("%d ",a[dui[i]].id); puts("");
}

inline void DP2(){//逆序跑一遍,方便连边
	for(int i=0;i<=n;i++) if(f[i]==ans) dp[i]=1; else dp[i]=-inf;
	for(int i=0;i<=n;i++) g[i]=-inf;
	t1.clear(); t2.clear(); t3.clear(); int l,r,last,pos; dp[0]=ans;
	for(int i=n;i>=1;i=l-1) {
		l=r=i; while(a[l-1].y==a[r].y) l--;
		for(int j=l;j<=r;j++) {
			last=t1.query(a[j].x+a[j].y); t1.insert(a[j].x+a[j].y,j); if(last==-1) continue;
			if(dp[last]+1>dp[j]) { dp[j]=dp[last]+1; from[j]=last; }
		}
		for(int j=l;j<=r;j++) {
			last=t2.query(a[j].x-a[j].y); t2.insert(a[j].x-a[j].y,j); if(last==-1) continue;
			if(dp[last]+1>dp[j]) { dp[j]=dp[last]+1; from[j]=last; }
		}
		for(int j=l;j<=r;j++) {
			last=t3.query(a[j].x); 	t3.insert(a[j].x,j); if(last==-1) continue;
			if(dp[last]+1>dp[j]) { dp[j]=dp[last]+1; from[j]=last; }
		}
		pos=l;
		for(int j=l+1;j<=r;j++) {
			if(dp[pos]+r-pos<dp[j-1]+r-j+1) pos=j-1;
			if(dp[pos]+r-pos>g[j]) g[j]=dp[pos]+r-pos;
		}
		pos=r;
		for(int j=r-1;j>=l;j--) {
			if(dp[pos]+pos-l<dp[j+1]+j+1-l) pos=j+1;
			if(dp[pos]+pos-l>g[j]) g[j]=dp[pos]+pos-l;
		}
		for(int j=l;j<=r;j++) dp[j]=max(dp[j],g[j]);
	}
}

inline void build(){//建图
	t1.clear(); t2.clear(); t3.clear(); a[0].x=a[0].y=0; int l,r,last;
	for(int i=n;i>=0;i=l-1) {
		l=le[belong[i]]; r=ri[belong[i]];
		for(int j=l;j<=r;j++) {
			last=t1.query(a[j].x+a[j].y); t1.insert(a[j].x+a[j].y,j); if(last==-1) continue;
			if(dp[last]+f[j]==ans) b[++hcnt].x=j,b[hcnt].y=last;
		}
		for(int j=l;j<=r;j++) {
			last=t2.query(a[j].x-a[j].y); t2.insert(a[j].x-a[j].y,j); if(last==-1) continue;
			if(dp[last]+f[j]==ans) b[++hcnt].x=j,b[hcnt].y=last;
		}
		for(int j=l;j<=r;j++) {
			last=t3.query(a[j].x); t3.insert(a[j].x,j); if(last==-1) continue;
			if(dp[last]+f[j]==ans) b[++hcnt].x=j,b[hcnt].y=last;
		}
	}

	ecnt=1; S=n+1; T=S+1;
	for(int i=1;i<=hcnt;i++) { in[b[i].x]--; in[b[i].y]++; link(b[i].x,b[i].y,inf); }
	for(int i=1;i<=n;i++) {
		if(in[i]>0) link(S,i,in[i]),tot+=in[i];
		else if(in[i]<0) link(i,T,-in[i]);
	}
}

inline bool bfs(){
	int head,tail,u; head=tail=0;
	for(int i=1;i<=T;i++) deep[i]=-1; deep[S]=0; dui[++tail]=S;
	while(head<tail) {
		u=dui[++head];
		for(int i=first[u];i;i=e[i].next) {
			if(e[i].f==0) continue;	int v=e[i].to; if(deep[v]!=-1) continue;
			deep[v]=deep[u]+1; dui[++tail]=v;
		}
	}
	if(deep[T]==-1) return false;
	return true;
}

inline int dinic(int x,int remain){
	if(x==T || remain==0) return remain;
	int flow=0,ff;
	for(int i=first[x];i;i=e[i].next) {
		if(e[i].f==0) continue; int v=e[i].to;
		if(deep[v]!=deep[x]+1) continue;
		ff=dinic(v,min(remain,e[i].f));
		if(ff==0) deep[v]=-1;
		else {
			flow+=ff; remain-=ff;
			e[i].f-=ff; e[i^1].f+=ff;
			if(remain==0) return flow;
		}
	}
	return flow;
}

inline void work(){
	n=getint(); for(int i=1;i<=n;i++) { a[i].x=getint(); a[i].y=getint(); a[i].id=i; }
	sort(a+1,a+n+1,cmpy); a[0].x=a[0].y=a[n+1].x=a[n+1].y=-inf;
	DP();
	Print();
	DP2();
	build();
	while(bfs())
		tot-=dinic(S,inf);
	printf("%d",tot);
}

int main()
{
    work();
    return 0;
}

  

时间: 2024-10-25 22:35:39

UOJ132 【NOI2015】小园丁与老司机的相关文章

【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° 五个方向之一. 沿此方

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

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文件与其它格式之间进行相互转化的情况.那么我们需要动手重新做吗?很显然,这是个吃力不讨好的且效率低下的选择,有经验的"老司机"这时会轻描淡写地说一声:"用转换器转一下就好." 现在市场上有很多这样的转换器,可大多数都不好用.国外的由于语言和操作

Oracle & MySQL 老司机说我们要使用(延迟复制)

最近工作中又遇到生产环境数据库的表被删除的情况,其实这样的事情本不该发生. 几个小建议: 生产环境数据库开发人员只能有查询权限,甚至级别低的开发根本没权限查生产系统,类似表的删除交给专业的DBA来操作,当然有些单位没有所谓的DBA. 专职的DBA基本都具备一个属性,就是每一步的操作都会考虑好后果,所以删除表之前都会有一个备份. 建立审核制度,truncate .drop. rm这样的操作可能是致命的,必须要审核. 可以先rename表,比如把表rename成bak_date_tablename,

老司机手把手教你如何吊妹子(硬件篇)

听说听了徐老师的课做了程序员以后钓不上妹子,这个锅甩得我只能给你负分.但是为人师表的我始终持有好人做到底送佛送到西的理念,所以今天我们不说iOS,由老司机带路,告诉众吊如何才能把到女神,吃上茶叶蛋,走上人生巅峰. 作为资深外貌协会的徐老师而言,找女朋友的第一点就是年轻漂亮,当然随着年龄的增长,对于女盆友的选择上也多少会有一些妥协,所以我现在选择女盆友的标准就是年轻,漂亮,性格好,身材好,肤白貌美,大长腿...... 不要以为只有徐老师才这么色,要知道女孩纸看男生的时候,也同样是先关注你的外貌的,

老司机学python篇:第一季(基础速过、机器学习入门)

详情请交流  QQ  709639943 00.老司机学python篇:第一季(基础速过.机器学习入门) 00.Python 从入门到精通 78节.2000多分钟.36小时的高质量.精品.1080P高清视频教程!包括标准库.socket网络编程.多线程.多进程和协程. 00.Django实战之用户认证系统 00.Django实战之企业级博客 00.深入浅出Netty源码剖析 00.NIO+Netty5各种RPC架构实战演练 00.JMeter 深入进阶性能测试体系 各领域企业实战 00.30天搞