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));
    }
}  

原文地址:https://www.cnblogs.com/PsychicBoom/p/10863665.html

时间: 2024-10-17 06:56:08

CF1083C Max Mex(线段树上二分)的相关文章

CF1083C Max Mex 线段树

题面 CF1083C Max Mex 题解 首先我们考虑,如果一个数x是某条路径上的mex,那么这个数要满足什么条件? 1 ~ x - 1的数都必须出现过. x必须没出现过. 现在我们要最大化x,那么也就意味着我们要找到一条路径使得这个都出现过的前缀尽可能长. 第二个条件可以忽略,因为如果第1个条件满足,而第2个条件却不满足,意味着我们可以把x至少扩大1位,因为要求最大值,所以扩大肯定最优,因此我们肯定会扩大到不能扩大为止. 由此我们可以发现,x是满足可二分性的. 考虑在线段树上维护这个问题,区

[CF1083C]Max Mex

题解 题目就是求树上路径的最大\(Mex\) 直接在树上维护这些东西难度有点大 但是\(Mex\)表示的是最小的没有出现过的自然数 这样我们就可以按照数为下标建立线段树 那么一个代表\([l,r]\)的线段树节点就代表了\([l,r]\)之间的这些数能否构成一条路径 注意:这里的能构成路径不是恰好能形成一条路径,而是不能确定一定不能形成一条路径 那么线段树的每个节点就还需要维护链的两个端点 然后合并信息的时候就是分类讨论 枚举两个端点,看剩下的两个点是否在这条路径上 就大致这么判断 return

【BZOJ】4293: [PA2015]Siano 线段树上二分

[题意]给定n棵高度初始为0的草,每天每棵草会长高a[i],m次收割,每次在d[i]天将所有>b[i]的草收割到b[i],求每次收割量.n<=500000. [算法]线段树上二分 [题解]按照生长速度a[]排序后,容易发现数列永远单调. 在线段树上的区间维护以下值: 1.最后一棵草的高度a 2.上次收割日期b 3.总的草高和c 4.总的生长速度和d 5.收割标记D和B 上传的时候注意右区间收割晚于左区间时强制合并. 下传的时候注意标记D和B直接覆盖. 线段树上二分: 1.判断当前区间是否符合(

UVALive - 8086 Substring Sorting (后缀数组+线段树上二分)

题意: 给一个串S, 多次询问k和m,求S的所有长度为k的不同子串中,字典序为排第m的串的最早出现位置 简化问题: 如果没有长度k的限制,并且没有不同子串的限制要怎么做.要字典序第m大,容易想到用后缀数组,因为它就是将n个后缀按字典序排好的,设f(i) = 排名<=i的所有后缀的所有前缀的个数和,假设答案的串是排名i的后缀的前缀,那么有f(i) >= k 且 f(i-1) < k,则满足二分性,可以二分后缀排名解决. 扩展: 有不同子串的限制,则类似求一个串有多少不同子串那样,对于每个排

CF817F MEX Queries(线段树上二分)

题意 维护一个01串,一开始全部都是0 3种操作 1.把一个区间都变为1 2.把一个区间都变为0 3.把一个区间的所有数字翻转过来 每次操作完成之后询问区间最小的0的位置 l,r<=10^18 题解 区间操作想到线段树,离散化不用说,l,r太大了. 1,2,3操作非常好维护. 然后在查询中二分查询就好了. 一开始看别的博客(对,我看题解了,太菜)说要加1节点和r+1节点不知道为什么. 因为我的查询想的是,查询前面全都是1的区间的长度.后来发现做不了.就乖乖照题解做了. 1 #include<i

Max Mex

Max Mex 无法直接处理 可以二分答案! [0,mid]是否在同一个链上? 可以不修改地做了 修改? 能不能信息合并?可以! 记录包含[l,r]的最短链的两端 可以[0,k][k+1,mid]合并:枚举四个端点中的两个,使得另外两个一定在这两个的路径上 (判断z点在x,y路径上:(lca(x,z)==z||lca(y,z)=z)&&(lca(lca(x,y),z)=lca(x,y))画图即可理解 能合并,所以线段树可以维护. 线段树维护 线段树上二分. LCA用ST表存 #includ

Blog Post Rating CodeForces - 806E (线段树二分)

题目链接 题目大意: 有一个博客, 初始分数为$0$, 有$n$个人, 第$i$个人有一个期望值$a_i$, 如果第$i$个人浏览博客时,博客赞数高于$a_i$博客分数$-1$, 低于$+1$, 相等不变, 对于每个$i$, 求出$[1,i]$的人按任意顺序浏览博客后最大分数. 题解: 首先, 用贪心可以知道所有人按期望升序排列, 最后得分一定最大 由于期望有负数, 博客分数一定是先减后增的, 然后对这两段分类讨论 对于递减的段, 最后分数为递增递减的临界值 假设临界值为$x$, 设比$x$小的

BZOJ3932(主席树上二分+差分

按时间作为主席树的版本,每个版本的主席树都是一个权值线段树. 差分消去时间影响 对于当前时间版本的主席树查询前K大即可. 树上二分时结束后切记判定l==r的状态(易错 l==r叶子节点可能存在多个值(值大小为sum/siz ) 用I64dOLE了好久 .. .... . .. . . . . . . . .. . 用bit/stdc++.h  CE..... #include<cmath> #include<cstdio> #include<cstring> #incl

Codeforces 487B. Strip(求区间最值+线段树上的dp)

B. Strip time limit per test 1 second memory limit per test 256 megabytes input standard input output standard output Alexandra has a paper strip with n numbers on it. Let's call them ai from left to right. Now Alexandra wants to split it into some p