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;}




0 0