CF1083C Max Mex 线段树

题面

CF1083C Max Mex

题解

首先我们考虑,如果一个数x是某条路径上的mex,那么这个数要满足什么条件?

  • 1 ~ x - 1的数都必须出现过.
  • x必须没出现过。

现在我们要最大化x,那么也就意味着我们要找到一条路径使得这个都出现过的前缀尽可能长。
第二个条件可以忽略,因为如果第1个条件满足,而第2个条件却不满足,意味着我们可以把x至少扩大1位,因为要求最大值,所以扩大肯定最优,因此我们肯定会扩大到不能扩大为止。
由此我们可以发现,x是满足可二分性的。

考虑在线段树上维护这个问题,区间\([l, r]\)表示值域区间为\([l, r]\)的点全都连接在一起形成的一条路径。
如果有这条路径,那么就存下左右端点,否则就不存。
那么考虑如何合并2个区间\([l, mid], [mid + 1, r]\)。
从4个端点中任取2个作为新的端点,如果另外2个端点均在这2个端点构成的路径上,那么我们就找到了一条新路径满足值域属于\([l, r]\)的点全在路径上。
这样做的正确性(最优性)依赖于权值两两不同,因此我们的选择是唯一的,因此肯定是最优选择。

有一个很简洁的式子可以用于判断一个点是否在某条路径上:
如果\(x\)在链\((u, v)\)上,设\(LCA(u, v) = y\)那么有:
\[((LCA(u, x) == x \oplus LCA(v, x) == x) \oplus LCA(x, y) == y)\]

于是维护好之后剩下的事情就是在线段树上查询(二分)了。

#include<bits/stdc++.h>
using namespace std;
#define R register int
#define LL long long
#define AC 201000
#define ac 802000
#define h(x) ((x) << 1)

int n, m;
int Head[AC], date[AC], Next[AC], tot;
int v[AC], fa[AC], s[AC];//s[i]表示权值为i的是哪个,只需要在最开始用就可以了

inline int read()
{
    int x = 0;char c = getchar();
    while(c > '9' || c < '0') c = getchar();
    while(c >= '0' && c <= '9') x = x * 10 + c - '0', c = getchar();
    return x;
}

inline void add(int f, int w)
{date[++ tot] = w, Next[tot] = Head[f], Head[f] = tot;}

#define cal(x, y) ((dep[x] < dep[y]) ? x : y)//返回x, y中dep最小的那个
#define maxn 401000
int st[maxn][20], ss[maxn], dep[AC], t[maxn], p[maxn], first[AC], top;
struct ST_form{

    void dfs(int x)//求遍历的数组 + dep
    {
        ss[++ top] = x, first[x] = top;
        for(R i = Head[x]; i; i = Next[i])
        {
            int now = date[i];
            dep[now] = dep[x] + 1, dfs(now), ss[++ top] = x;//返回的时候也要加
        }
    }

    void build()
    {
        dep[1] = 1, dfs(1);
        int tmp = 1, cnt = 0;
        for(R i = 1; i <= top; i ++)
        {
            if(i == (tmp << 1)) tmp <<= 1, ++ cnt;
            p[i] = tmp, t[i] = cnt;
        }
        for(R i = 1; i <= top; i ++) st[i][0] = ss[i];
        tmp = 1;
        for(R i = 1; i <= 18; i ++)
        {
            for(R j = 1; j <= top; j ++)
                st[j][i] = cal(st[j][i - 1], st[min(j + tmp, top)][i - 1]);
            tmp <<= 1;
        }
    }

    inline int LCA(int x, int y)//st表求LCA
    {
        int l = first[x], r = first[y];
        if(l > r) swap(l, r);//不一定按顺序的
        int len = r - l + 1;
        return cal(st[l][t[len]], st[r - p[len] + 1][t[len]]);
    }
}T;

struct node{
    int lx, rx;
}tree[ac], go;

#define update(x) tree[x] = merge(tree[x << 1], tree[(x << 1) + 1]);
struct seg_tree{

    bool check(int x, int u, int v)//检查x是否在路径(u, v)上
    {
        int y = T.LCA(u, v);
        return ((T.LCA(u, x) == x || T.LCA(v, x) == x) && T.LCA(x, y) == y);
    }

    inline node merge(node ll, node rr)
    {
        if(!ll.lx || !rr.lx) return (node){0, 0};
        int s[5] = {ll.lx, ll.rx, rr.lx, rr.rx};
        for(R i = 0; i < 4; i ++)
            for(R j = i + 1; j < 4; j ++)
            {
                bool flag = true;
                if(s[i] == s[j]) continue;
                for(R k = 0; k < 4; k ++)
                    if(k != i && k != j && !check(s[k], s[i], s[j])) {flag = false; break;}
                if(flag) return (node){s[i], s[j]};
            }
        return (node){0, 0};
    }

    void build(int x, int l, int r)
    {
        if(l == r) {tree[x].lx = tree[x].rx = s[l]; return ;}
        int mid = (l + r) >> 1;
        build(h(x), l, mid), build(h(x) + 1, mid + 1, r);
        update(x);
    }

    void change(int x, int l, int r, int w)
    {
        if(l == r) {tree[x].lx = tree[x].rx = w; return ;}
        int mid = (l + r) >> 1;
        if(v[w] <= mid) change(h(x), l, mid, w);
        else change(h(x) + 1, mid + 1, r, w);
        update(x);
    }

    int find(int x, int l, int r)
    {
        int mid = (l + r) >> 1;
        if(l == r)
        {
            node tmp = go.lx ? merge(go, tree[x]) : tree[x];
            if(tmp.lx) {go = tmp; return 1;}
            return 0;
        }
        if(!tree[h(x)].lx) return find(h(x), l, mid);
        else
        {
            node tmp = go.lx ? merge(go, tree[h(x)]) : tree[h(x)];
            if(!tmp.lx) return find(h(x), l, mid);
            else {go = tmp; return mid - l + 1 + find(h(x) + 1, mid + 1, r);}
        }
    }

}T1;

void pre()
{
    n = read();
    for(R i = 1; i <= n; i ++) v[i] = read() + 1, s[v[i]] = i;
    for(R i = 2; i <= n; i ++) fa[i] = read(), add(fa[i], i);
}

void work()
{
    m = read();
    for(R i = 1; i <= m; i ++)
    {
        int opt = read();
        if(opt == 1)
        {
            int x = read(), y = read();
            swap(v[x], v[y]);
            T1.change(1, 1, n, x), T1.change(1, 1, n, y);
        }
        else go.lx = go.rx = 0, printf("%d\n", T1.find(1, 1, n));//因为向右平移了1,所以要移回来,+ 1 - 1就恰好抵消了
    }
}

int main()
{
//  freopen("in.in", "r", stdin);
    pre();
    T.build();
    T1.build(1, 1, n);
    work();
//  fclose(stdin);
    return 0;
}

原文地址:https://www.cnblogs.com/ww3113306/p/10474350.html

时间: 2024-09-30 03:32:01

CF1083C Max Mex 线段树的相关文章

CF1083C Max Mex(线段树上二分)

这题卡倍增害我T了一发= = 显然Mex是可以二分的,于是就可以考虑二分一个Mex然后check一下 然后怎么check呢?可以对点权建一棵线段树,节点\([l,r]\)表示,链上点权的集合包括\([l,r]\)时,最短的链的端点 合并两个区间就是在四个端点间选两个作为新链的端点,判断另外两个端点在不在这条链上,在的话这就是一条合法的链.判断方法就是判断一下两段的距离是否等于一整条链的距离. 这样时间复杂度是\(O(nlog^2n)\),感觉可过的样子?然而还可以在线段树上二分把时间复杂度优化到

hdu4747 mex 线段树

题意:给一个序列不超过200000个元素,定义mex(i,j)是区间[i,j]之间所没有的最小非负整数.求sum(mex[i,j])对于所有1<=i<=j<=n; 解法:线段树.先求出mex(1,1),mex(1,2),mex(1,3)...mex(1,n) 而且这必然是递增的. 然后 sum[i=1,1<=j<=n]就算出来了,然后去掉arr[1],这时候会影响到的是下一个arr[1]出现前mex值大于arr[1]的那些位置,而且由于mex具有单调性,如果有必然是连续的一个

[CF1083C]Max Mex

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

BZOJ.3585.mex(线段树)

题目链接 考虑\([1,i]\)的\(mex[i]\),显然是单调的 而对于\([l,r]\)与\([l+1,r]\),如果\(nxt[a[l]]>r\),那么\([l+1,r]\)中所有\(>a[l]\)的数显然要改成\(a[l]\) 询问排序,离散化,预处理下nxt[],剩下就是线段树的区间更新.查询了 /* 离散化的时候>=n的全部看做n就好了 查询时是只需查r点的(l之前能更新r的已经更新完了,初始时是[1,r],r点现在就是[l,r]了) 单点即可不需要PushUp(也不好得某

hdu 4747 Mex( 线段树? 不,区间处理就行(dp?))

Mex Time Limit: 15000/5000 MS (Java/Others)    Memory Limit: 65535/65535 K (Java/Others)Total Submission(s): 3056    Accepted Submission(s): 1006 Problem Description Mex is a function on a set of integers, which is universally used for impartial game

线段树区间修改模板

本来打算把大白书第三章一口气攻下来的,但是这个线段树也是卡了好久. 不敢过题太快,怕自己走马观花到头来结果什么都不会. 可也不能再拖了,在做题中也许有更多的体会. 模板一:1 L R v 表示区间[L, R]所有元素都加上v2 L R   表示查询区间[L, R]的sum, min, maxsumv[o]的定义为:如果只执行节点o及其子孙节点的中的add操作,节点o对应区间中所有数之和 1 //线段树区间修改 2 //1 L R v 表示区间[L, R]所有元素都加上v 3 //2 L R 表示

线段树---HDU1166敌兵布阵

这个是线段树中最入门的题目,但是由于不了解线段树的概念,当然更不知道怎么样,所以觉得挺费劲,整了一会发现还是基本的思想,就是还是将一个线段继续分割,一直分割到不能分割,这道题目是知道多少个军营,也就是区间为1-n, 将它分割, 建立树, 可以不用保存它区间的左端点和右端点,用数组下标代表就可以了, 数组的值代表当前军营里人的个数,然后这个题就是单个点的增加或者减少,其实增加减少都是增加,减少只是把增加的数目变成负数就行了,还有就是更新完最下面的点还要一直往上更新.那样查找区间的时候才不会出错.下

SDUSTOJ 1796 哆啦A梦的军队(线段树维护前缀位置)

Description 在2050年机器人战争爆发,聪明的机器猫为了帮助大雄打赢这场战 争,从自己口袋里掏出了机器人战棋,每一个战棋都可以成为一名战士,哆啦A梦决定给他们整整队,哆啦A梦发现第 i 个位置的战士编号为 Ai(显然 A 是一个排列).经过计算,哆啦A梦发现,让第 i 个位置的战士编号为 Bi 时,他的军队可以发挥出最大的战斗力(保证 B 也是一个排列). 哆啦A梦可以发出指令来改变战士们的排列顺序,每一次,他都会报出一个整数 i(1≤i<n).如果排在第 i 个位置的战士编号大于第

Mex(线段树的巧妙应用)

题目要求求某段区间第一个没有出现的数(0,1,2,3....) ,对于所有的区间,我们把这样的数加起来最后得到一个结果. 首先,我们要求出这样的数,然后还得列举出所有的区间,复杂度太大了. 换种思路,我们定住L,是不是一次性能求出所有的R所得出的结果,这就用到线段树的性质了,因为在移动L的过程中,移一步只变化一个数,那么就可以用线段树进行维护. 首先求出[1,R] 以1为左端的所有区间的情况,记录每个点也就是1到那个点的这段区间值sum[i],以这个值建一颗树,那么在L向前移动的时候,每次丢掉一