bzoj 4358: permu (莫队+栈||KD-tree||莫队+线段树)

15 篇文章 0 订阅
12 篇文章 0 订阅

题目描述

传送门

题目大意:给出一个长度为n的排列P(P1,P2,…Pn),以及m个询问。每次询问某个区间[l,r]中,最长的值域
连续段长度。

题解1:莫队+线段树

用权值线段树维护区间的连续最大长度,左端连续最长,右端连续最长。
应该是比较好想,好写,好调的做法。
时间复杂度 O(nnlogn) ,实际测速中貌似会TLE,于是利用毕生所学进行卡常,最终卡时A掉了。。。。

代码1

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cmath>
#include<cstring>
#define N 50003
using namespace std;
int n,m,a[N],belong[N],block,ans[N],pos[N];
struct data{
    int id,x,y,key;
}q[N];
struct node{
    int ls,rs,tr,len;
}tree[N*4];
int read()
{
    char c=getchar();  int x=0;
    while (c<'0'||c>'9') c=getchar();
    while (c>='0'&&c<='9') x=x*10+c-'0',c=getchar();
    return x;
}
inline bool operator <(const data &a,const data &b){
    return a.key<b.key||a.key==b.key&&((a.y<b.y)^(a.key&1));
}
inline void update(int now)
{
    node *T=tree+now,*L=tree+(now<<1),*R=tree+(now<<1|1);
    T->tr=max(L->tr,R->tr);
    T->tr=max(L->rs+R->ls,T->tr);
    T->ls=L->ls+(L->ls==L->len)*R->ls;
    T->rs=R->rs+(R->rs==R->len)*L->rs;
}
inline void pointchange(int x)
{
    int t=pos[x];
    tree[t].ls=tree[t].rs=(tree[t].tr^=1);
    while (t!=1) update(t=t>>1);
}
inline void build(int now,int l,int r)
{
    tree[now].len=r-l+1;
    if (l==r) {
     pos[l]=now;
     return;
    }
    int mid=(l+r)/2;
    build(now<<1,l,mid);
    build(now<<1|1,mid+1,r);
}
int main()
{
    freopen("a.in","r",stdin);
    freopen("my.out","w",stdout);
    n=read(); m=read();
    for (int i=1;i<=n;i++) a[i]=read();
    for (int i=1;i<=m;i++)
     q[i].x=read(),q[i].y=read(),q[i].id=i,q[i].key=sqrt(q[i].x);
    sort(q+1,q+m+1);
    build(1,1,n);
    int l,r; l=r=1; pointchange(a[1]);
    for (int i=1;i<=m;++i) {
        while (l<q[i].x) pointchange(a[l++]);
        while (l>q[i].x) pointchange(a[--l]);
        while (r>q[i].y) 
         pointchange(a[r--]);
        while (r<q[i].y) 
         pointchange(a[++r]);
        ans[q[i].id]=tree[1].tr;
    }
    for (int i=1;i<=m;i++) printf("%d\n",ans[i]);
}


题解2:莫队+栈

依然是用莫队,可以终于可以不用线段树了。
将左端点分块,每个块中的右端点从小到大有序。对于左端点在一个块中的一起处理。
刚开始将莫队的左右端点都定位在块的右端点。
对于每个权值的数维护两个参ls,rs,表示向左,右延伸的最长距离。
ls[x]=ls[x1]+1
rs[x]=rs[x+1]+1
然后用 ls[x]+rs[x]1 更新答案。注意还要用此时的答案取更新这段连续权值区间的左右端点,因为在加入点一定是利用左右端点去更新。
如果当前询问的右端点<当前的右端点,那么就将当前的右端点后移。并按照上述方式更新。
然后我们考虑当前询问的左端点到还不在当前区间中的点的贡献。因为左端点的前后移动是不固定的,所以我们每次加入这些点的贡献,计算出当前询问的贡献,然后需要再回撤这些操作。保证每次左端点都停在块的右端点。
因为同一块的左端点的移动区间不超过 O(n) ,所以最后的时间复杂度是 O(mn)

代码2

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
#define N 100003
using namespace std;
int c[N],st[N],st1[N],ls[N],rs[N],n,m,block,belong[N],ans[N];
struct data{
    int l,r,id;
}q[N];
int cmp(data a,data b)
{
    return belong[a.l]<belong[b.l]||belong[a.l]==belong[b.l]&&a.r<b.r;
}
int main()
{
    freopen("a.in","r",stdin);
    freopen("my.out","w",stdout);
    scanf("%d%d",&n,&m); block=sqrt(n);
    for (int i=1;i<=n;i++) scanf("%d",&c[i]);
    for (int i=1;i<=n;i++) belong[i]=(i-1)/block+1;
    for (int i=1;i<=m;i++) {
        scanf("%d%d",&q[i].l,&q[i].r);
        q[i].id=i;
    }
    sort(q+1,q+m+1,cmp); int l,r,ans1; 
    for (int i=1;i<=m;i++) {
        if (belong[q[i].l]!=belong[q[i-1].l]) {
            memset(ls,0,sizeof(ls));
            memset(rs,0,sizeof(rs));
            l=r=belong[q[i].l]*block; ans1=0;
        }
        while (r<q[i].r) {
            ++r;
            ls[c[r]]=ls[c[r]-1]+1;
            rs[c[r]]=rs[c[r]+1]+1;
            int t=ls[c[r]]+rs[c[r]]-1;
            rs[c[r]-ls[c[r]]+1]=t;
            ls[c[r]+rs[c[r]]-1]=t;
            ans1=max(ans1,t);
        }
        int tmp=ans1,top=0;
        for (int j=q[i].l;j<=min(q[i].r,l);j++) {
            ls[c[j]]=ls[c[j]-1]+1;
            rs[c[j]]=rs[c[j]+1]+1;
            int t=ls[c[j]]+rs[c[j]]-1;
            int ll=c[j]-ls[c[j]]+1; int rr=c[j]+rs[c[j]]-1;
            st[++top]=ll; st1[top]=rs[ll];
            st[++top]=rr; st1[top]=ls[rr];
            rs[ll]=t; ls[rr]=t;
            tmp=max(tmp,t);
        }
        for (int j=top;j>=1;j--) {
            if (j&1) rs[st[j]]=st1[j];
            else ls[st[j]]=st1[j];
        }
        for (int j=q[i].l;j<=min(q[i].r,l);j++) 
         ls[c[j]]=rs[c[j]]=0;
        ans[q[i].id]=tmp;
    }
    for (int i=1;i<=m;i++) printf("%d\n",ans[i]);
}


题解3:KD-tree

将每个询问的左右端点看做点对(x,y),加入KD-tree
然后从1到n依次用这些数取更新包含他原本所在位置的区间。
对于KD-tree中的每个节点需要维护六个与更新有关的参数,具体的写法和bzoj 3064是一样的,只不过那个是线段树,这个是KD-tree,反正都是pushdown,就不详细赘述了。
注意每个不包含当前数所在位置的区间,当前连续的长度都要清零。
时间复杂度 O(nm) ,应该比做法一快不少才对,可能是我pushdown写的太丑了,所以最后两个的时间差不多,so sad……

代码3

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#define N 100003
using namespace std;
int a[N],pos[N],n,m,cmpd,ans[N],root,T[2];
struct data{
    int d[2],mx[2],mn[2],l,r,id;
    int cover,add,at,max,len,ct;
}tr[N];
bool operator <(data a,data b)
{
    return a.d[cmpd]<b.d[cmpd];
}
int read()
{
    char c=getchar();  int x=0;
    while (c<'0'||c>'9') c=getchar();
    while (c>='0'&&c<='9') x=x*10+c-'0',c=getchar();
    return x;
}
void update(int now)
{
    int l=tr[now].l; int r=tr[now].r;
    for (int i=0;i<=1;i++) {
        if (l) tr[now].mx[i]=max(tr[now].mx[i],tr[l].mx[i]),tr[now].mn[i]=min(tr[now].mn[i],tr[l].mn[i]);
        if (r) tr[now].mx[i]=max(tr[now].mx[i],tr[r].mx[i]),tr[now].mn[i]=min(tr[now].mn[i],tr[r].mn[i]);
    }
}
int build(int l,int r,int d)
{
    cmpd=d; int mid=(l+r)/2;
    nth_element(tr+l,tr+mid,tr+r+1);
    for (int i=0;i<=1;i++) tr[mid].mx[i]=tr[mid].mn[i]=tr[mid].d[i];
    tr[mid].cover=-1; tr[mid].ct=-1;
    if (l<mid) tr[mid].l=build(l,mid-1,d^1);
    if (r>mid) tr[mid].r=build(mid+1,r,d^1);
    update(mid);
    return mid;
}
void pushdown(int now)
{
    T[0]=tr[now].l; T[1]=tr[now].r;
    for (int i=0;i<=1;i++) {
        int x=T[i]; if(!x) continue;
        tr[x].max=max(tr[x].max,max(tr[now].ct,tr[x].len+tr[now].at));
        if (tr[x].cover==-1) tr[x].at=max(tr[x].at,tr[now].at+tr[x].add);
        else tr[x].ct=max(tr[x].ct,tr[x].cover+tr[now].at);
        if (tr[now].add) {
            tr[x].len+=tr[now].add; 
            if (tr[x].cover==-1) tr[x].add+=tr[now].add;
            else tr[x].cover+=tr[now].add;
        }
        if (tr[now].cover!=-1) {
            tr[x].cover=tr[x].len=tr[now].cover;
            tr[x].add=0;
        }
        tr[x].ct=max(tr[x].ct,max(tr[x].cover,tr[now].ct));
        tr[x].at=max(tr[x].at,tr[x].add);
    }
    tr[now].at=tr[now].add=0;
    tr[now].cover=tr[now].ct=-1;
}
void change(int now,int x)
{
    pushdown(now);
    if (tr[now].mx[0]<=x&&x<=tr[now].mn[1]) {
     tr[now].len++; tr[now].add++; tr[now].at++;
     tr[now].max=max(tr[now].max,tr[now].len); return;
    }
    if (tr[now].mx[1]<x||tr[now].mn[0]>x) {
     tr[now].cover=tr[now].ct=tr[now].len=0; return;
    }
    if (tr[now].d[0]<=x&&x<=tr[now].d[1]) {
        tr[now].len++;
        tr[now].max=max(tr[now].max,tr[now].len);
    }
    else tr[now].len=0;
    if(tr[now].l) change(tr[now].l,x);
    if(tr[now].r) change(tr[now].r,x);
}
void dfs(int now)
{
    pushdown(now);
    if (tr[now].l) dfs(tr[now].l);
    if (tr[now].r) dfs(tr[now].r);
}
int main()
{
    freopen("a.in","r",stdin);
    freopen("my.out","w",stdout);
    n=read(); m=read();
    for (int i=1;i<=n;i++) a[i]=read(),pos[a[i]]=i;
    for (int i=1;i<=m;i++)
     tr[i].d[0]=read(),tr[i].d[1]=read(),tr[i].id=i;
    root=build(1,m,0);
    for (int i=1;i<=n;i++)
      change(root,pos[i]); 
    dfs(root);
    for (int i=1;i<=m;i++) ans[tr[i].id]=tr[i].max;
    for (int i=1;i<=m;i++) printf("%d\n",ans[i]);
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值