bzoj4527

本文介绍了一种算法,用于从给定数列中找到最长的连续子串,该子串通过添加最多k个数后可形成公差为d的等差数列。采用线段树维护最大最小值及特定标记,实现高效查找。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值