[JZOJ4663]Seq

6 篇文章 0 订阅
5 篇文章 0 订阅

题目大意

给出一个长度为 n 的排列,有m个询问,每次询问区间 [l,r] ,求这个区间内最长的值域连续段(最长一段数值,使得该段数值的所有数都在区间内出现)长度。

1n,m50000


题目分析

这题考察我们对莫队算法的灵活运用。
显然使用莫队算法加上线段树维护最大连续子段可以做到 O(nnlog2n) ,然而这个算法太慢了。
我们考虑如果所有操作都是插入操作,那么答案是不下降的,每次操作我们可以 O(1) 维护联通块左右端点进行更新。问题是这题里面会有删除操作。
考虑对莫队算法进行小的修改,分块依据依然不变,然而在处理每一块的询问时,我们始终让左指针先位于块位,右指针和普通莫队一样递增地扫,处理询问时,我们可以让左指针向左调整,显然这些操作都是插入操作,可以 O(1) 处理。
问题是我们同一块内询问左段不一定单调不上升,因此我们需要还原我们的插入操作。乍一看好像还是要打删除,其实不然,答案我们可以直接在处理询问之前存好,暴力赋值还原,而对于联通块的维护,我们可以记录每次合并的块的左右端点和中间点,然后 O(1) 还原(这个自己思考吧,不会看代码)。
然后还要注意如果左指针在右指针右边,需要特殊处理。我是强制限定当左指针在右指针左端(或重合)时才执行插入,这样就可以简洁明了地处理所有情况了。
时间复杂度 O(nn)


代码实现

#include <algorithm>
#include <iostream>
#include <cstring>
#include <cstdio>
#include <cctype>
#include <cmath>

using namespace std;

int read()
{
    int x=0,f=1;
    char ch=getchar();
    while (!isdigit(ch)) f=ch=='-'?-1:f,ch=getchar();
    while (isdigit(ch)) x=x*10+ch-'0',ch=getchar();
    return x*f;
}

const int N=50050;
const int M=50050;

int lp[N],rp[N];
bool mark[N];
int ans[M];
int n,m,s;
int p[N];

struct Q
{
    int l,r,id;
}q[M];

bool operator<(Q x,Q y){return (x.l-1)/s<(y.l-1)/s||(x.l-1)/s==(y.l-1)/s&&x.r<y.r;}

int did[N][3];
int cnt;
int now,lcur,rcur;

void change(int x,bool flag)
{
    if (lcur<=rcur)
    {
        int delta=0,y;
        x=p[x];
        mark[x]=true,lp[x]=rp[x]=x;
        if (flag) cnt++,did[cnt][0]=did[cnt][1]=did[cnt][2]=x;
        if (x&&mark[x-1]) delta+=x-lp[x-1],lp[x]=lp[x-1],rp[lp[x-1]]=x,flag?did[cnt][0]=lp[x-1]:0;
        if (x+1<=n&&mark[x+1]) y=lp[x],delta+=rp[x+1]-x,rp[y]=rp[x+1],lp[rp[x+1]]=y,flag?did[cnt][1]=rp[x+1]:0;
        now=max(now,delta+1);
    }
}

void recover(int u)
{
    int x=did[u][2],y=did[u][0],z=did[u][1];
    mark[x]=false;
    if (y<x) rp[y]=x-1,lp[x-1]=y;
    if (x<z) rp[x+1]=z,lp[z]=x+1;
}

void solve()
{
    int tmp,tail;
    for (int i=1;i<=m;i++)
    {
        if (i==1||(q[i-1].l-1)/s!=(q[i].l-1)/s)
        {
            memset(mark,0,sizeof mark);
            tail=min(((q[i].l-1)/s+1)*s,n)+1;
            now=0,lcur=tail,rcur=0;
        }
        while (rcur<q[i].r) change(++rcur,0);
        cnt=0,tmp=now;
        while (lcur>q[i].l) change(--lcur,1);
        ans[q[i].id]=now,now=tmp,lcur=tail;
        for (;cnt;) recover(cnt--);
    }
}

int main()
{
    freopen("seq.in","r",stdin),freopen("seq.out","w",stdout);
    n=read(),m=read(),s=trunc(sqrt(n))+1;
    for (int i=1;i<=n;i++) p[i]=read();
    for (int i=1;i<=m;i++) q[i].l=read(),q[i].r=read(),q[i].id=i;
    sort(q+1,q+1+m);
    solve();
    for (int i=1;i<=m;i++) printf("%d\n",ans[i]);
    fclose(stdin),fclose(stdout);
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值