ZOJ 3740 —— Water Level(DP+线段树)
来源:互联网 发布:js替换字符串中的数字 编辑:程序博客网 时间:2024/06/09 19:06
题目链接
题目的大概意思是,首先给一个正整数N,给一个序列ai,这个序列共N个数字,并且每个数字都在[-N,N]这个区间上。
一次针对ai的操作的作用是,使得ai到aN所有数字都加上c,c是任意并且可正可负。
问最多执行两次操作,也可以不操作,操作的对象也是任意的,问这个序列最多可以有多少个数字落在[1,N]这个区间上。
先说明我的做法的复杂度是O(N*N*logN),跑了1700+MS水过;
不知道跑了200+MS的大牛是怎么做的,Orz
为了方便计算,我在输入的时候先将每个数字加上N+1,这样数字的范围就变成了[1,2N+1]。
对于不操作的情况,那就是直接统计。
只操作一次的话,就是分两段,前面一段没改变的和后面一段被改变的,然后枚举断点取最大值即可。
而操作两次,设两次操作的位置分别是i和j,i<j,因为c是可以任意的,那么无论前面i操作的c是多少,j位置的c都可以根据前面的情况进行调整。
所以这种情况分为三段:前面未被改变的 、 ai到aj-1 和 aj到aN。然后还是枚举断点取最大值。
剩下的问题就是怎么求某个区间的最优值。
因为一次操作使得某个区间上的数字的改变量是一样的,我们的目标是使得较多的数字落在[1,N]上,其实也就是把ai到aj上的数字全部放到数轴上,然后在数轴上找一段长度为N-1的,并且落在上面的数字最多,通过c使得它平移到跟[1,N]重合。
这里我用线段树做统计。
解释下线段树中结点o的定义:
l[o]和r[o],是区间的左右端点;
这里要解释的是,对于这里的线段上的点,代表的是一个长度为N-1的区间。
比如N=2的时候,经过前面的修正,ai的范围应该是[1,5],假设当前数字是1和4
那么,我们要找的答案的范围应该就是[1,2]、[2,3]、[3,4]和[4,5]这样四个区间,这四个区间对应的个数就是1,0,1,1;
取它们的左端点的值1,2,3,4作为端点保存到线段树中,这样就可以通过左端点记录对应的区间的个数,从而求最值,对应的线段树的结构如下,右边是对应结点的最大值:
s[o]保存当前结点的最值,f[o]是懒惰标记。
这样当插入一个新的ai时,因为ai可以落到很多个长度为N的区间上,比如增加一个数字是3,3落在[2,3]和[3,4]两个区间,相当于对应的起点都加1,这些起点还是连续的,这就是个区间修改问题(上面的例子就是线段树区间[2,3]加1),修改子节点维护好父节点信息,就可以直接从根节点取最大值。
只是我的做法必须枚举所有的i和j,
具体处理是,可以先从右边往左扫,可以先计算出所有的ai到aN的值,保存到dp[i]中。
然后就是for循环扫过去枚举中间的ai和aj,每次都固定ai,清空线段树之后,不断增加新的aj进去。
所以复杂度是O(N*N*logN),N只有3000,估着大概卡着时间能过吧,实际运行也差不多,2000MS的题目跑了1700+MS。高效的就不知道怎么写了。
#include<cstdio>#include<cstring>#include<algorithm>using namespace std;#define MAXN 3010#define LEFT(o) ((o)<<1)#define RIGHT(o) (((o)<<1)|1)#define MID(a,b) (((a)+(b))>>1)int n, i, j, ans, cur, re, dp[MAXN], a[MAXN], r[MAXN<<2], l[MAXN<<2], s[MAXN<<2], f[MAXN<<2];inline void getnum(int& x){ x=0; bool mk=0; char c=getchar(); while(c<48 || c>57){ if(c=='-') mk=1; c=getchar(); } while(c>=48 && c<=57){ x=x*10+c-48; c=getchar(); } if(mk) x=-x;}void build(int o, int ll, int rr){ l[o]=ll; r[o]=rr; if(ll<rr){ int m=MID(ll,rr); build(LEFT(o),ll,m); build(RIGHT(o),m+1,rr); }}void maintain(int o){ s[o] = max(s[LEFT(o)], s[RIGHT(o)]);}void update(int o, int ll, int rr, int v){ if(l[o]==ll && r[o]==rr){ f[o]+=v; s[o]+=v; } else{ int lc=LEFT(o), rc=RIGHT(o); int m=MID(l[o],r[o]); if(f[o]){ update(lc,l[o],m,f[o]); update(rc,m+1,r[o],f[o]); f[o]=0; } if(rr<=m) update(lc,ll,rr,v); else if(ll>m) update(rc,ll,rr,v); else{ update(lc,ll,m,v); update(rc,m+1,rr,v); } maintain(o); }}int main(){ while(~scanf("%d", &n)){ memset(s,0,sizeof(s)); for(i=1; i<=n; i++){ getnum(a[i]); a[i]+=n+1; } build(1,1,n+1); memset(s,0,sizeof(s)); memset(f,0,sizeof(f)); for(i=n; i>=1; i--){ update(1, max(1,a[i]-n+1), min(n+1, a[i]),1); dp[i]=s[1]; } re=0; ans = 0; for(i=1; i<=n; i++){ memset(s,0,sizeof(s)); memset(f,0,sizeof(f)); ans = max(ans, re+dp[i]);//只操作一次 for(j=i+1; j<=n; j++){//枚举操作两次 update(1, max(1,a[j-1]-n+1), min(n+1, a[j-1]),1); ans = max(ans, re+dp[j]+s[1]); } re += (a[i]>n+1?1:0); } ans = max(ans, re);//不操作 printf("%d\n", ans); } return 0;}
- ZOJ 3740 —— Water Level(DP+线段树)
- [dp] zoj 3740 Water Level
- zoj 3632 Watermelon Full of Water(DP+线段树)
- Watermelon Full of Water - ZOJ 3632 dp+线段树
- ZOJ-3632 Watermelon Full of Water 线段树+DP
- ZOJ 3632 Watermelon Full of Water(dp+线段树或单调队列优化)
- TOJ 4369 ZOJ 3632 Watermelon Full of Water / 线段树优化DP
- ZOJ 3632 Watermelon Full of Water (线段树 区间更新 + dp)
- zoj 2900 DP(线段树优化)
- ZOJ 3349线段树+DP
- ZOJ 3632 Watermelon Full of Water 线段树
- ZOJ 3349 Special Subsequence(DP+线段树优化)
- ZOJ 3650 Toy Blocks(DP + 线段树优化转移)
- ZOJ 3349 Special Subsequence(线段树优化DP)
- zoj 3349 Special Subsequence(dp+线段树优化)
- ZOJ 3637Education Manage System (dp+线段树)
- ZOJ 3349 Special Subsequence(DP+线段树优化)
- ZOJ 3650 Toy Blocks(DP + 线段树优化转移)
- 揭秘硬件
- JSON串:{"date":28,"day":3,"hours":5...转String 转Date 的相转方法
- Windows下的很多程序都有十分漂亮的菜单
- oracle jdk 1.6 下载链接
- php腾讯QQ 第三方登录 回调问题
- ZOJ 3740 —— Water Level(DP+线段树)
- UVa:10617 Again Palindrome
- 使用vs2010的基本设置
- 20分钟读懂程序集
- Hibernate深度学习笔记
- ubuntu编译安装静态库时遇到的问题,yacc和lex的安装
- 网络设备闲谈
- 听"易中天品三国"---有感于曹操的用人之道
- Hibernate配置文件属性学习笔记