bzoj4358 permu(莫队+线段树||莫队+并查集+分块||K-D tree)

很久以前的坑=,= 题意:给你一个排列序列,每次询问问l,r区间内最长的值域连续段长度。范围50000,长得就像莫队-,-。怎么转移呢,首先有一个比较明显的做法,用数值建一棵线段树,维护最大子段和。这样的复杂度是 (nnlogn) 的,但是常数巨大,不太好卡过去。。。按堆建树,从下往上推,蜜汁分块大小,居然卡过去了。欣慰,附上代码。然而正解是这样的:我们对每一个数都记一下现在他所属的值域连续段的最左端和最右端,这样就可以用并查集来维护了,但是并查集并不支持删除操作,只能插入,所以我们还得必须保证往并查集里只能插值,不能往回走。那怎么办呢?莫队排序后,我们知道,对于左端点在同一块内的若干查询,它们的右端点是单调增的,所以我们可以不断把右端点更新进并查集。那左端点呢?左端点不可避免的要有往回走的情况,所以我们只好每次对块内的点都暴力更新答案,拿另一个并查集来做,每次做完就清空。这样我们就可以通过两个并查集来维护答案了。这样的复杂度应该能控制在 O(nn)

2018.1.13upd:学习了一下K-D tree的做法。

莫队+线段树版

#include<cstdio>
#include<cstring>
#include<cmath>
#include<algorithm>
#define N 50005
int a[N],n,m,block,ANS[N],pos[N];
using namespace std;
struct segtree{
    int len,sl,sr,s;
}tree[N<<2];
struct query{
    int l,r,id,block;
}q[N];
inline int max(int x,int y){return x>y?x:y;}
inline int read(){
    int x=0;char ch=getchar();
    while(ch<'0'||ch>'9') ch=getchar();
    while(ch>='0'&&ch<='9') x=x*10+ch-'0',ch=getchar();
    return x;
}
inline bool cmp(query x,query y){
    return x.block<y.block||x.block==y.block&&((x.r<y.r)^(x.block&1));
}
inline void pushup(int p){
    tree[p].sl=tree[p<<1].sl+(tree[p<<1].sl==tree[p<<1].len)*tree[p<<1|1].sl;
    tree[p].sr=tree[p<<1|1].sr+(tree[p<<1|1].sr==tree[p<<1|1].len)*tree[p<<1].sr;
    tree[p].s=max(tree[p<<1].s,tree[p<<1|1].s);
    tree[p].s=max(tree[p].s,tree[p<<1].sr+tree[p<<1|1].sl);
}
void build(int p,int l,int r){
    tree[p].len=r-l+1;
    if(tree[p].len==1){
        tree[p].sl=tree[p].sr=tree[p].s=0;
        pos[l]=p;return;
    }
    int mid=(l+r)>>1;
    build(p<<1,l,mid);
    build(p<<1|1,mid+1,r);
}
inline void change(int x){
    int p=pos[x];
    tree[p].sl=tree[p].sr=(tree[p].s^=1);
    while(p!=1) pushup(p=p>>1);
}
int main(){
//  freopen("a.in","r",stdin);
    n=read();m=read();//block=sqrt(n);
    for(int i=1;i<=n;++i) a[i]=read();
    build(1,1,n);
    for(int i=1;i<=m;++i){
        q[i].l=read();q[i].r=read();q[i].id=i;
        q[i].block=sqrt(q[i].l);
    }
    sort(q+1,q+m+1,cmp);
    int l=1,r=0;
    for(int i=1;i<=m;++i){
        if(q[i].l>q[i].r){
             ANS[q[i].id]=0;continue;
        }
        for(;l<q[i].l;++l) change(a[l]);
        for(;l>q[i].l;--l) change(a[l-1]);
        for(;r>q[i].r;--r) change(a[r]);
        for(;r<q[i].r;++r) change(a[r+1]);
        ANS[q[i].id]=tree[1].s;
    }
    for(int i=1;i<=m;++i) printf("%d\n",ANS[i]);
    return 0;
}

莫队+并查集版

#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cmath>
using namespace std;
#define N 50010
int a[N],n,m,ANS[N],stack[N],top=0,block,bl[N],ed[1000],ans=0;
int fa[N],size[N],fa2[N],mx[N],mn[N];
struct query{
    int l,r,id;
}q[N];
inline int read(){
    int x=0,f=1;char ch=getchar();
    while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
    while(ch>='0'&&ch<='9') x=x*10+ch-'0',ch=getchar();
    return x*f;
}
inline bool cmp(query x,query y){
    return bl[x.l]==bl[y.l]?x.r<y.r:x.l<y.l;
}
inline int find(int x){
    return x==fa[x]?x:fa[x]=find(fa[x]);
}
inline int find2(int x){
    return x==fa2[x]?x:fa2[x]=find2(fa2[x]);
}
inline void add(int u){
    int v;fa[u]=u;size[u]=1;
    if(v=find(u-1)) fa[v]=u,size[u]+=size[v];
    if(v=find(u+1)) fa[v]=u,size[u]+=size[v];
    if(size[u]>ans) ans=size[u];
}
inline void add2(int u,int &res){
    int v;fa2[u]=u;mn[u]=mx[u]=u;stack[++top]=u;
    if(v=find2(u-1)) mn[u]=mn[v];
    else if(v=find(u-1)) mn[u]-=size[v];
    if(v=find2(u+1)) mx[u]=mx[v];
    else if(v=find(u+1)) mx[u]+=size[v];
    if(mx[u]-mn[u]+1>res) res=mx[u]-mn[u]+1;
    if(mx[u]!=u) fa2[mx[u]]=u,stack[++top]=mx[u];
    if(mn[u]!=u) fa2[mn[u]]=u,stack[++top]=mn[u];
}
int main(){
//  freopen("a.in","r",stdin);
    n=read();m=read();block=sqrt(n);
    for(int i=1;i<=n;++i) a[i]=read(),bl[i]=(i-1)/block,ed[bl[i]]=i;
    for(int i=1;i<=m;++i){
        q[i].l=read();q[i].r=read();q[i].id=i;
    }sort(q+1,q+m+1,cmp);int p;
    for(int i=1;i<=m;i=p){//每次左端点到新块时重新做 
        memset(fa,0,sizeof(fa));ans=0;
        p=i+1;while(bl[q[i].l]==bl[q[p].l]) ++p;
        int cnt=ed[bl[q[i].l]];
        for(int j=i;j<p;++j){
            while(cnt<q[j].r) add(a[++cnt]);//右端点是单调的,只加不删,用并查集维护 
            ANS[q[j].id]=ans;
            //左端点不一定单调,所以块内的点每次都重新算,算完就清零,所以要用一个新的并查集 
            for(int k=q[j].l;k<=q[j].r&&k<=ed[bl[q[j].l]];++k) add2(a[k],ANS[q[j].id]);
            while(top) fa2[stack[top--]]=0;//用一个栈记录要撤销的点,提高效率 
        }
    }
    for(int i=1;i<=m;++i) printf("%d\n",ANS[i]);
    return 0;
}

K-D tree

把每一个询问看做一个二维的点,建立K-D tree,每个点维护一个计数器。按从1~n的顺序把每个数的位置加入,对于所有包含他的询问,计数器+1,其他的清0。最后每一个点的答案就是计数器的历史最大值。可以像线段树那样来维护,相当于有区间加,区间覆盖,求单点历史最大值。类似bzoj3964

#include <cstdio>
#include <cstring>
#include <algorithm>
#include <queue>
using namespace std;
#define ll long long
#define inf 0x7f7f7f7f
#define N 50010
inline int read(){
    int x=0,f=1;char ch=getchar();
    while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
    while(ch>='0'&&ch<='9') x=x*10+ch-'0',ch=getchar();
    return x*f;
}
int n,m,pos[N],rt=0,D,ans[N];
struct point{
    int d[2],id;
    int& operator[](int x){return d[x];}
    friend bool operator<(point a,point b){return a[D]<b[D];}
}P[N];
struct node{
    point x;int lc,rc,mx[2],mn[2],mxv,add,cov,hmx,hadd,hcov;
}tr[N];
inline void update(int p){
    int l=tr[p].lc,r=tr[p].rc;
    for(int i=0;i<2;++i){
        tr[p].mx[i]=max(tr[p].mx[i],max(tr[l].mx[i],tr[r].mx[i]));
        tr[p].mn[i]=min(tr[p].mn[i],min(tr[l].mn[i],tr[r].mn[i]));
    }
}
inline void build(int &p,int l,int r,int op){
    int mid=l+r>>1;p=mid;D=op;tr[p].mxv=tr[p].hmx=tr[p].add=tr[p].hadd=0;
    nth_element(P+l,P+mid,P+r+1);tr[p].x=P[mid];tr[p].cov=tr[p].hcov=-inf;
    for(int i=0;i<2;++i) tr[p].mn[i]=tr[p].mx[i]=tr[p].x[i];
    if(l<mid) build(tr[p].lc,l,mid-1,op^1);
    if(r>mid) build(tr[p].rc,mid+1,r,op^1);update(p);
}
inline bool in(int p,int x){
    return tr[p].mx[0]<=x&&tr[p].mn[1]>=x;
}
inline bool out(int p,int x){
    return tr[p].mn[0]>x||tr[p].mx[1]<x;
}
inline bool jud(point y,int x){
    return y[0]<=x&&y[1]>=x;
}
inline void doadd(int p,int val){
    if(!p) return;
    tr[p].hmx=max(tr[p].hmx,tr[p].mxv+=val);
    if(tr[p].cov==-inf) tr[p].hadd=max(tr[p].hadd,tr[p].add+=val);
    else tr[p].hcov=max(tr[p].hcov,tr[p].cov+=val);
}
inline void docov(int p,int val){
    if(!p) return;
    tr[p].hmx=max(tr[p].hmx,tr[p].mxv=val);
    tr[p].hcov=max(tr[p].hcov,tr[p].cov=val);
}
inline void dohadd(int p,int val){
    if(!p) return;
    tr[p].hmx=max(tr[p].hmx,tr[p].mxv+val);
    if(tr[p].cov==-inf) tr[p].hadd=max(tr[p].hadd,tr[p].add+val);
    else tr[p].hcov=max(tr[p].hcov,tr[p].cov+val);
}
inline void dohcov(int p,int val){
    if(!p) return;
    tr[p].hmx=max(tr[p].hmx,val);tr[p].hcov=max(tr[p].hcov,val);
}
inline void pushdown(int p){
    int l=tr[p].lc,r=tr[p].rc;
    if(tr[p].hadd) dohadd(l,tr[p].hadd),dohadd(r,tr[p].hadd),tr[p].hadd=0;
    if(tr[p].hcov!=-inf) dohcov(l,tr[p].hcov),dohcov(r,tr[p].hcov),tr[p].hcov=-inf;
    if(tr[p].add) doadd(l,tr[p].add),doadd(r,tr[p].add),tr[p].add=0;
    if(tr[p].cov!=-inf) docov(l,tr[p].cov),docov(r,tr[p].cov),tr[p].cov=-inf;

}
inline void ask(int p,int x){
    if(!p) return;
    if(in(p,x)){doadd(p,1);return;}
    if(out(p,x)){docov(p,0);return;}pushdown(p);
    if(jud(tr[p].x,x)) tr[p].hmx=max(tr[p].hmx,tr[p].mxv+=1);
    else tr[p].mxv=0;
    ask(tr[p].lc,x);ask(tr[p].rc,x);
}
inline void dfs(int p){
    pushdown(p);
    if(tr[p].lc) dfs(tr[p].lc);
    if(tr[p].rc) dfs(tr[p].rc);
}
int main(){
//  freopen("a.in","r",stdin);
    n=read();m=read();
    for(int i=0;i<2;++i) tr[0].mx[i]=0,tr[0].mn[i]=inf;
    for(int i=1;i<=n;++i) pos[read()]=i;
    for(int i=1;i<=m;++i) P[i][0]=read(),P[i][1]=read(),P[i].id=i;
    build(rt,1,m,0);
    for(int i=1;i<=n;++i) ask(rt,pos[i]);
    dfs(rt);for(int i=1;i<=m;++i) ans[tr[i].x.id]=tr[i].hmx;
    for(int i=1;i<=m;++i) printf("%d\n",ans[i]);
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值