题意:
我们称一个数列为一个好的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 210000
using 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也可以在线段树上找。注意询问的区间不能包含相同的数。