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. shell编程之awk

    awk是一种用于处理数据和生成报告的编程语言 awk可以在命令行中进行一些简单的操作,也可以被写成脚本来处理较大的应用问题 awk与grep.sed结合使用,将使shell编程更加容易 awk工作模式 ...

  2. ABBYY FineReader文档对比实例讲解

    ABBYY FineReader企业版与标准版有一个重大的区别,就是企业版有对比文档的功能,今天通过下面的文档对比实例,来向大家展示一下OCR文字识别工具ABBYY FineReader的使用技巧. ...

  3. 通用于wps和excel的ntlm hashes窃取利用方式

    https://evi1cg.me/archives/Get_NTLM_Hashes.html介绍了通过Microsoft Office 窃取 NTLM Hashes. 不过这种插入方法不适用于wps ...

  4. 基于gin的golang web开发:永远不要相信用户的输入

    作为后端开发者我们要记住一句话:"永远不要相信用户的输入",这里所说的用户可能是人,也可能是另一个应用程序."永远不要相信用户的输入"是安全编码的准则,也就是说 ...

  5. java中String类的使用

    一.Strng类的概念 String类在我们开发中经常使用,在jdk1.8版本之前(包括1.8),String类的底层是一个char类型的数组,1.8版本之后是byte类型的数组,正是因为String ...

  6. appium每次运行不初始化

    最近测试公司软件遇到一个问题,每次运行Appium测试代码,打开软件都是初始化,每次的新手教程以及无法记住密码一堆问题,导致效率很低,而且定位有时候出问题. 先看初始化的代码 1 from appiu ...

  7. Leetcode1 - 10

    1. 两数之和 class Solution { public: vector<int> twoSum(vector<int>& nums, int target) { ...

  8. P3287 [SCOI2014]方伯伯的玉米田

    首先可以证明,一定存在一种最优解,每次选择的区间结尾都是 \(n\).因为如果某一个区间结尾不是 \(n\),将其替换成 \(n\) 仍然保持单调不下降.接着都按这个策略拔高玉米. 令 \(f_{i, ...

  9. X86中断/异常与APIC

    异常(exception)是由软件或硬件产生的,分为同步异常和异步异常.同步异常即CPU执行指令期间同步产生的异常,比如常见的除零错误.访问不在RAM中的内存 .MMU 发现当前虚拟地址没有对应的物理 ...

  10. [Android systrace系列] systrace入门第一式

    转载请注明出处:https://www.cnblogs.com/zzcperf/p/13978915.html Android systrace是分析性能问题最称手的工具之一,可以提供丰富的手机运行信 ...