CF1083C Max Mex(线段树上二分)

Posted psychicboom

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了CF1083C Max Mex(线段树上二分)相关的知识,希望对你有一定的参考价值。

这题卡倍增害我T了一发= =

显然Mex是可以二分的,于是就可以考虑二分一个Mex然后check一下

然后怎么check呢?可以对点权建一棵线段树,节点\([l,r]\)表示,链上点权的集合包括\([l,r]\)时,最短的链的端点

合并两个区间就是在四个端点间选两个作为新链的端点,判断另外两个端点在不在这条链上,在的话这就是一条合法的链。判断方法就是判断一下两段的距离是否等于一整条链的距离。

这样时间复杂度是\(O(nlog^2n)\),感觉可过的样子?然而还可以在线段树上二分把时间复杂度优化到\(O(nlogn)\)

大概就是:如果\([l,r]\)区间合法,直接合并起来,否则往左扩展。如果左半边的区间全部都合并起来了就可以往右扩展。

下面就是代码实现啦

int query(int l,int r,int o,qwq &x){
    if(t[o].u&&t[o].v){
        if(!x.u){ x=t[o];return r; }//第一次合并
        qwq tmp=x+t[o];
        if(tmp.u){ x=tmp;return r; } //[l,r]区间合法
    }
    if(l==r){return (x+t[o]).u?l:0; }
    int mid=l+r>>1;
    int res=query(lson,x);
    if(res<mid) return res;//不能向右合并
    else return max(query(rson,x),res);//取max是考虑[1,mid]不能往后合并的情况
}

代码:

#include <bits/stdc++.h>
#define N 200005
#define bas 1,n,1
#define lson l,mid,(o<<1)
#define rson mid+1,r,(o<<1|1)
#define pb push_back
using namespace std;

int a[N],b[N],dep[N],cnt=0,top[N],sz[N],fa[N],son[N];
vector<int>g[N];

void dfs1(int x){
    sz[x]=1,dep[x]=dep[fa[x]]+1;
    for(int i=0;i<g[x].size();++i){
        dfs1(g[x][i]);
        sz[x]+=sz[g[x][i]];
        if(sz[g[x][i]]>sz[son[x]]) son[x]=g[x][i];
    }
}
void dfs2(int x,int s){
    top[x]=s;
    if(!son[x]) return;
    dfs2(son[x],s);
    for(int i=0;i<g[x].size();++i)
        if(g[x][i]!=son[x]) dfs2(g[x][i],g[x][i]);
}
int lca(int x,int y){
    while(top[x]!=top[y]){
        if(dep[top[x]]<dep[top[y]]) swap(x,y);
        x=fa[top[x]];
    }
    return dep[x]<dep[y]?x:y; 
}
int dis(int x,int y){ return dep[x]+dep[y]-2*dep[lca(x,y)]; }
bool pd(int x,int y,int z){ return dis(x,y)==dis(x,z)+dis(y,z); }

struct qwq{
    int u,v;
    qwq operator +(const qwq &a)const{
        if(!u||!v||!a.u||!a.v) return (qwq){0,0};
        if(pd(a.u,u,v) && pd(a.u,u,a.v)) return (qwq){a.u,u};
        if(pd(a.u,v,u) && pd(a.u,v,a.v)) return (qwq){a.u,v};
        if(pd(a.u,a.v,u) && pd(a.u,a.v,v)) return (qwq){a.u,a.v};
        if(pd(u,a.v,a.u) && pd(u,a.v,v)) return (qwq){u,a.v};       
        if(pd(u,v,a.u) && pd(u,v,a.v)) return (qwq){u,v};
        if(pd(a.v,v,a.u) && pd(a.v,v,u)) return (qwq){a.v,v};
        return (qwq){0,0};      
    }
} t[N<<2],pmt;

void build(int l,int r,int o){
    if(l==r){ t[o]=(qwq){b[l],b[l]};return; }
    int mid=(l+r)>>1;t[o]=(qwq){0,0};
    build(lson);
    build(rson);
    t[o]=t[o<<1]+t[o<<1|1];
}
void update(int l,int r,int o,int x){
    if(l==r){ t[o]=(qwq){b[l],b[l]};return; }
    int mid=(l+r)>>1;
    if(x<=mid) update(lson,x);
    else update(rson,x);
    t[o]=t[o<<1]+t[o<<1|1];
}
int query(int l,int r,int o,qwq &x){
    if(t[o].u&&t[o].v){
        if(!x.u){ x=t[o];return r; }
        qwq tmp=x+t[o];
        if(tmp.u){ x=tmp;return r; } 
    }
    if(l==r){return (x+t[o]).u?l:0; }
    int mid=l+r>>1;
    int res=query(lson,x);
    if(res<mid) return res;
    else return max(query(rson,x),res); 
}

int main(){
    int n,x,q,i,op,y;scanf("%d",&n);
    for(i=1;i<=n;++i) scanf("%d",&a[i]),a[i]++,b[a[i]]=i;
    for(i=2;i<=n;++i) scanf("%d",&fa[i]),g[fa[i]].pb(i);
    dfs1(1),dfs2(1,1);
    build(bas);
    scanf("%d",&q);
    while(q--){
        scanf("%d",&op);
        if(op==1){
            scanf("%d%d",&x,&y);
            swap(b[a[x]],b[a[y]]),swap(a[x],a[y]);
            update(bas,a[x]),update(bas,a[y]);
        } else pmt=(qwq){0,0},printf("%d\n",query(bas,pmt));
    }
}  

以上是关于CF1083C Max Mex(线段树上二分)的主要内容,如果未能解决你的问题,请参考以下文章

线段树CF1083C Max Mex

CF1083C Max Mex 线段树

CF817F MEX Queries(线段树上二分)

CF1083C

Rmp(Mex) & Destiny & 楼房重建(线段树上二分)

[模板] 区间mex && 区间元素种数