Description

给定一颗 \(n\) 个顶点的树,顶点 \(i\) 有点权 \(p_i\)。其中 \(p_1,p_2,\cdots, p_n\) 为一个 \(0\sim (n-1)\) 的一个排列。

有 \(q\) 次操作,每次操作:

  • \(\texttt{1 x y}\):交换 \(x, y\) 两顶点的点权;
  • \(\texttt{2}\):求树上所有路径上的点权构成的集合的 \(\text{MEX}\) 中最大的。其中 \(S\) 集合的 \(\text{MEX}\) 为其中最小的没有在 \(S\) 中出现过的自然数。

Hint

\(1\le n, q\le 2\times 10^5\)。

Solution

线段树与树的船新结合,orz。

在所有路径的 \(\text{MEX}\) 中,若出现过 \(\text{MEX}=x\),那么说明存在一条路径,上面出现了 \(0, 1, \cdots, (x-1)\) 所有这些点权。考虑到题目中 \(p\) 为一个排列,那么一个特定的点权只存在一个点与之对应,这给我们提供的很大的方便。

维护 \(\text{MEX}\),根据经验可以考虑对权值建立线段树。然后我们发现满足 \(\text{MEX} = x\) 的极小路径最多只有一条,于是可以让线段树的一个结点直接维护一条路径。具体来讲,对于值域区间 \([l, r]\) 的线段树结点,维护一条路径 \(s\to t\) 满足路径上包含了 \(l, (l+1), \cdots , r\) 所有这些权值,并且 \(s\to t\) 是最小的满足这个条件的路径。

如何实现两个结点的合并?假设左右儿子对应的路径分别为 \(s_l\to t_l, s_r\to t_r\),然后我们需要找到最小的一条同时包含这两条路径的路径,当然要注意可能不存在。

既然最小,毫无疑问就是取 \(s_l, s_r, t_l, t_r\) 这四个端点的其二作为新端点 \(s, t\),其余两个都必须位于路径 \(s\to t\) 上。于是枚举这两个小端点就完事了,反正是常数级别。

接下来只要做到快速判断一个点 \(x\) 是否位于路径 \(s\to t\) 即可。首先 \(x\) 必须是顶点 \(s, t\) 至少其中一个的祖先(或本身),然后 \(x\) 还不得高于 \(\text{LCA}(s, t)\)。总之需要满足——

\[((\text{LCA}(s, x)=x)\or (\text{LCA}(t, x)=x))\land(\text{LCA}(s, t)=\text{LCA}(x, \text{LCA}(s, t)))
\]

由于这里求 \(\text{LCA}\) 的次数较多,于是尽量用较快的求法(ST 表,树剖)。

然后是查询。不难发现这玩意有可二分性,那么就二分答案 \(x\),然后判断包含 \([0, x)\) 的路径是否存在即可。但这样是一次 \(O(\log^2 n)\),虽说应该也可以但我们有 \(O(\log n)\) 的方法。

考虑线段树上二分,在这里我们还需要在线段树上走是,维护一下前面一段 \([0, x)\) 对应的路径 \(P\)。

具体地,对于一个结点,先看看自己当前值域下的路径,如果存在,那么尝试能不能将这个路径直接和 \(P\) 合并。如果可以我们就直接走掉。

不行的话,就考虑左区间是否存在覆盖整段左值域区间的路径。如果没有,那么右边自然不必考虑,因为前面不连续,后半再连续也毫无意义;反之如果左边有,那么再考虑右边,之后就是递归的事了。

于是这道题就 \(O((n+q)\log n)\) AC 了(LCA 使用 ST 表)。

Code

/*
* Author : _Wallace_
* Source : https://www.cnblogs.com/-Wallace-/
* Problem : Codeforces 1083C Max Mex
*/
#include <array>
#include <algorithm>
#include <cctype>
#include <cstdio>
#include <vector>
#include <utility> using namespace std;
const int N = 4e5 + 5;
const int logN = 20;
inline int read() {
int x = 0; char c; while (!isdigit(c = getchar()));
do x = (x << 1) + (x << 3) + c - 48; while (isdigit(c = getchar()));
return x;
} int n, m, fa[N];
int val[N], loc[N];
vector<int> adj[N]; namespace LCA {
int fir[N], dep[N], lg2[N], timer = 0;
pair<int, int> f[N][logN];
void dfs(int x) {
f[fir[x] = ++timer][0] = make_pair(dep[x] = dep[fa[x]] + 1, x);
for (auto y : adj[x]) if (y != fa[x]) dfs(y), f[++timer][0] = make_pair(dep[x], x);
}
void init() {
dfs(1), lg2[0] = -1;
for (int i = 1; i <= timer; i++) lg2[i] = lg2[i >> 1] + 1;
for (int j = 1; j < logN; j++) for (int i = 1; i + (1 << j) - 1 <= timer; i++)
f[i][j] = min(f[i][j - 1], f[i + (1 << j - 1)][j - 1]);
}
int get(int x, int y) {
if (x <= 0 || y <= 0) return 0;
x = fir[x], y = fir[y]; if (x > y) swap(x, y);
int t = lg2[y - x + 1];
return min(f[x][t], f[y - (1 << t) + 1][t]).second;
}
} bool in_path(int s, int t, int x) {
int l = LCA::get(s, t);
return (x == LCA::get(s, x) || x == LCA::get(t, x))
&& (l == LCA::get(x, l));
} typedef array<int, 2> Path;
const Path emp = {-1, -1};
namespace segt {
#define mid ((l + r) >> 1)
Path dat[N << 2]; Path merge(Path a, Path b) {
if (a == emp || b == emp) return emp;
for (int i = 0; i < 2; i++) for (int j = 0; j < 2; j++) {
int s = a[i], t = b[j], p = a[i ^ 1], q = b[j ^ 1];
if (in_path(s, t, p) && in_path(s, t, q)) return {s, t};
}
if (in_path(a[0], a[1], b[0]) && in_path(a[0], a[1], b[1])) return a;
if (in_path(b[0], b[1], a[0]) && in_path(b[0], b[1], a[1])) return b;
return emp;
}
void build(int x, int l, int r) {
if (l == r) { dat[x] = {loc[l], loc[l]}; return; }
build(x << 1, l, mid), build(x << 1 | 1, mid + 1, r);
dat[x] = merge(dat[x << 1], dat[x << 1 | 1]);
}
void fix(int x, int l, int r, int p) {
if (l == r) { dat[x] = {loc[l], loc[l]}; return; }
(p <= mid) ? fix(x << 1, l, mid, p) : fix(x << 1 | 1, mid + 1, r, p);
dat[x] = merge(dat[x << 1], dat[x << 1 | 1]);
}
int query(int x, int l, int r, Path& p) {
if (dat[x] != emp) {
if (p == emp) { p = dat[x]; return r + 1; }
Path f = merge(p, dat[x]);
if (f != emp) { p = f; return r + 1; }
}
if (l == r) return l;
int ret = query(x << 1, l, mid, p);
if (ret <= mid) return ret;
else return query(x << 1 | 1, mid + 1, r, p);
}
#undef mid
} signed main() {
n = read();
for (int i = 1; i <= n; i++) loc[val[i] = read()] = i;
for (int i = 2; i <= n; i++) adj[fa[i] = read()].push_back(i);
LCA::init(), segt::build(1, 0, n - 1); m = read();
while (m--) {
int opt = read();
if (opt == 1) {
int x = read(), y = read();
swap(loc[val[x]], loc[val[y]]), swap(val[x], val[y]);
segt::fix(1, 0, n - 1, val[x]), segt::fix(1, 0, n - 1, val[y]);
} else {
Path tmp = emp;
printf("%d\n", segt::query(1, 0, n - 1, tmp));
}
}
}

【Codeforces 1083C】Max Mex(线段树 & LCA)的更多相关文章

  1. Codeforces 1083C Max Mex [线段树]

    洛谷 Codeforces 思路 很容易发现答案满足单调性,可以二分答案. 接下来询问就转换成判断前缀点集是否能组成一条链. 我最初的想法:找到点集的直径,判断直径是否覆盖了所有点,需要用到树套树,复 ...

  2. Codeforces 1083C Max Mex

    Description 一棵\(N\)个节点的树, 每个节点上都有 互不相同的 \([0, ~N-1]\) 的数. 定义一条路径上的数的集合为 \(S\), 求一条路径使得 \(Mex(S)\) 最大 ...

  3. CF1083C Max Mex 线段树

    题面 CF1083C Max Mex 题解 首先我们考虑,如果一个数x是某条路径上的mex,那么这个数要满足什么条件? 1 ~ x - 1的数都必须出现过. x必须没出现过. 现在我们要最大化x,那么 ...

  4. 51 nod 1766 树上的最远点对(线段树+lca)

    1766 树上的最远点对 基准时间限制:3 秒 空间限制:524288 KB 分值: 80 难度:5级算法题   n个点被n-1条边连接成了一颗树,给出a~b和c~d两个区间,表示点的标号请你求出两个 ...

  5. Buses and People CodeForces 160E 三维偏序+线段树

    Buses and People CodeForces 160E 三维偏序+线段树 题意 给定 N 个三元组 (a,b,c),现有 M 个询问,每个询问给定一个三元组 (a',b',c'),求满足 a ...

  6. [Codeforces 1197E]Culture Code(线段树优化建图+DAG上最短路)

    [Codeforces 1197E]Culture Code(线段树优化建图+DAG上最短路) 题面 有n个空心物品,每个物品有外部体积\(out_i\)和内部体积\(in_i\),如果\(in_i& ...

  7. [Codeforces 1199D]Welfare State(线段树)

    [Codeforces 1199D]Welfare State(线段树) 题面 给出一个长度为n的序列,有q次操作,操作有2种 1.单点修改,把\(a_x\)修改成y 2.区间修改,把序列中值< ...

  8. csu 1798(树上最远点对,线段树+lca)

    1798: 小Z的城市 Time Limit: 5 Sec  Memory Limit: 128 MBSubmit: 60  Solved: 16[Submit][Status][Web Board] ...

  9. BZOJ 2588: Spoj 10628. Count on a tree-可持久化线段树+LCA(点权)(树上的操作) 无语(为什么我的LCA的板子不对)

    2588: Spoj 10628. Count on a tree Time Limit: 12 Sec  Memory Limit: 128 MBSubmit: 9280  Solved: 2421 ...

随机推荐

  1. 手写atoi、strcpy、strcat

    一:实现atoi函数 1 #include<iostream> 2 3 using namespace std; 4 5 int atoi_my(const char *str) 6 { ...

  2. 内存使用过高点检checklist

    正在运行的程序按照内存段来组织,内存段的类型有如下几种: 代码段:用户程序指令,长期存在内存中 数据段:全局变量等,长期存在内存中 堆:局部变量,参数参数等,短期存在内存中 栈:动态存储,可变 代码段 ...

  3. C/C++编程日记:逻辑井字棋(圈叉)(用空格初始化)

    问题描述: 3*3的棋盘中,只要一条线上出现三个一样的棋子就获胜(玩家或电脑):如果棋盘已经放满还未出现三个棋子一条线则打成平手. 具体细节: 初始化棋盘(用空格初始化)     //初始化棋盘 vo ...

  4. 这份java多线程笔记,你真得好好看看,我还没见过总结的这么全面的

    1.线程,进程和多线程 1.程序:指指令和数据的有序集合,其本身没有任何意义,是一个静态的概念 2.进程:指执行程序的一次执行过程,是一个动态的概念.是系统资源分配的单位(注意:很多多线程是模拟出来的 ...

  5. MathType中如何实现上下两行公式“=”号对齐

    作为功能强大的数学公式编辑器,MathType可以轻松输入各种复杂的公式和符号,与 Office 文档完美结合,显示效果超好,比 Office 自带的公式编辑器要强大很多,可以为办公文档.网页.桌面出 ...

  6. Vegas教程:教你制作抖音热门人物穿越门窗特效

    抖音上经常会有很多特效视频,例如换妆.分镜.合拍.放大等,合适的特效总是会让视频更加出彩.这些特效,除了一部分是抖音自带以外,很多都是用的其他视频特效软件制作而成.这些视频编辑软件操作简单易上手,强大 ...

  7. 理解go语言的shellcode加载器

    序言 本文假设你知道unsafe包常见函数的用法,若否,请查看 https://books.studygolang.com/gopl-zh/ch13/ch13-01.html  第13章. 例子和代码 ...

  8. 使用logisim搭建单周期CPU与添加指令

    使用logisim搭建单周期CPU与添加指令 搭建 总设计 借用高老板的图,我们只需要分别做出PC.NPC.IM.RF.EXT.ALU.DM.Controller模块即可,再按图连线,最后进行控制信号 ...

  9. JavaSE 学习笔记08丨网络编程

    Chapter 14. 网络编程 14.1 计算机网络入门 当前节的知识点只是一个概述,更具体.详细的内容放在 计算机网络 中. 14.1.1 软件结构 C/S结构(Client/Server结构): ...

  10. websocket服务端开发

    基于http请求以拉的方式去做服务器的推送,无论是实时性和有效字节都是差强人意的效果. 公司的im系统在与客户端的交互上实际上借助了websocket来实现服务器与客户端的事实消息推送,今天就来简单了 ...