bzoj4527

来源:互联网 发布:悦诗风吟雪耳面霜知乎 编辑:程序博客网 时间:2024/06/11 17:51

题意:
我们称一个数列为一个好的k-d数列,当且仅当我们在其中加上最多k个
数之后,数列排序后为一个公差为d的等差数列。
你手上有一个由n个整数组成的数列a。你的任务是找到它的最长连续子
串,使得满足子串为好的k-d数列。
1<=n<=200000;0<=k<=200000;0<=d<=10^9

#include<cstring>#include<cstdlib>#include<cstdio>#include<cmath>#include<iostream>#include<map>#define N 210000using namespace std;struct node{int l,r,lc,rc,d1,d2,d,lz1,lz2;}lt[2*N];struct node1{int l,r,d;}s1[N],s2[N];int n,k,d,ansl,ansr,a[N],b[N],tl,t1,t2,lim;map<int,int> ma;void updans(int l,int r){    if(r-l>ansr-ansl) ansl=l,ansr=r;}void solve1(){    int l=1;    for(int i=2;i<=n;i++)    {        if(a[i]!=a[i-1]) l=i;        updans(l,i);    }}void down(int now){    int lc=lt[now].lc,rc=lt[now].rc;    if(lt[now].lz1!=-1 && lt[now].lz2!=-1)    {        lt[now].d=lt[now].lz1-lt[now].lz2+lt[now].l;        lt[now].d1=lt[now].lz1+lt[now].l;        lt[now].d2=-lt[now].lz2+lt[now].l;        lt[lc].lz1=lt[now].lz1;lt[lc].lz2=lt[now].lz2;        lt[rc].lz1=lt[now].lz1;lt[rc].lz2=lt[now].lz2;    }    else if(lt[now].lz1!=-1)    {        lt[now].d=lt[now].d2+lt[now].lz1;        lt[now].d1=lt[now].lz1+lt[now].l;        lt[lc].lz1=lt[now].lz1;lt[rc].lz1=lt[now].lz1;    }    else if(lt[now].lz2!=-1)    {        lt[now].d=lt[now].d1-lt[now].lz2;        lt[now].d2=-lt[now].lz2+lt[now].l;        lt[lc].lz2=lt[now].lz2;lt[rc].lz2=lt[now].lz2;    }    lt[now].lz1=lt[now].lz2=-1;}void upd(int now){    int lc=lt[now].lc,rc=lt[now].rc;    down(lc);down(rc);    lt[now].d=min(lt[lc].d,lt[rc].d);    lt[now].d1=min(lt[lc].d1,lt[rc].d1);    lt[now].d2=min(lt[lc].d2,lt[rc].d2);}void bt(int l,int r){    int now=++tl;    lt[now].l=l;lt[now].r=r;lt[now].lz1=lt[now].lz2=-1;    if(l<r)    {        int mid=(l+r)/2;        lt[now].lc=tl+1;bt(l,mid);        lt[now].rc=tl+1;bt(mid+1,r);        upd(now);    }    else lt[now].d1=lt[now].d2=lt[now].d=l;}void change(int now,int l,int r,int d,int o){    int lc=lt[now].lc,rc=lt[now].rc,mid=(lt[now].l+lt[now].r)/2;    down(now);    if(lt[now].l==l && lt[now].r==r)    {        if(o==1) lt[now].lz1=d;        else lt[now].lz2=d;        return;    }    if(mid>=r) change(lc,l,r,d,o);    else if(l>mid) change(rc,l,r,d,o);    else change(lc,l,mid,d,o),change(rc,mid+1,r,d,o);    upd(now);}int find(int now,int l,int r){    int lc=lt[now].lc,rc=lt[now].rc,mid=(lt[now].l+lt[now].r)/2;    down(now);    if(lt[now].d>lim) return -1;    if(lt[now].l==lt[now].r) return lt[now].l;    if(mid>=r) return find(lc,l,r);    else if(l>mid) return find(rc,l,r);    else    {        int t=find(lc,l,mid);        if(t==-1) t=find(rc,mid+1,r);        return t;    }}void solve(int st,int ed){    t1=t2=0;    int pre=st;    for(int i=st;i<=ed;i++)    {        node1 t=(node1){i,i,a[i]/d};        while(t1 && s1[t1].d<t.d) {t.l=s1[t1].l;t1--;}        s1[++t1]=t;        change(1,s1[t1].l,s1[t1].r,s1[t1].d,1);        t=(node1){i,i,a[i]/d};        while(t2 && s2[t2].d>t.d) {t.l=s2[t2].l;t2--;}        s2[++t2]=t;        change(1,s2[t2].l,s2[t2].r,s2[t2].d,2);        if(ma.count(a[i])) pre=max(pre,ma[a[i]]+1);        lim=i+k;        int l=find(1,pre,i);        updans(l,i);        ma[a[i]]=i;    }}void solve2(){    bt(1,n);    for(int i=1;i<=n;i++) b[i]=a[i]%d;    int st=1;    while(st<n)    {        int ed=st;        while(ed<=n && b[ed]==b[st]) ed++;        ed--;        solve(st,ed);        st=ed+1;    }}int main(){    scanf("%d%d%d",&n,&k,&d);    ansl=ansr=1;    for(int i=1;i<=n;i++) scanf("%d",&a[i]);    int t=a[1];    for(int i=2;i<=n;i++) t=min(t,a[i]);    for(int i=1;i<=n;i++) a[i]+=abs(t);    if(d==0) solve1();    else solve2();    printf("%d %d\n",ansl,ansr);    return 0;}

题解:
特判d=0
对于d!=0,模d的余数相同的子串才能作为答案。对d同余子串一个个处理。
令ai=floor(ai/d)
考虑枚举答案右端点i,那么左端点j的这个区间满足什么条件才合法?分析一下就知道是:
(max-min+1)-(i-j+1)<=k

max-min+j<=i+k
于是线段树维护。i右移时单调栈找出修改max和min的区间,线段树上存两个标记,维护max-min+j,max+j,-min+j最小值就可以down了。
对于i找一个最远的j也可以在线段树上找。注意询问的区间不能包含相同的数。

1 0
原创粉丝点击