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. Netlink 内核实现分析 2

    netlink 应用层如何创建socket 应用层通过socket()系统调用创建Netlink套接字,socket系统调用的第一个参数可以是AF_NETLINK或PF_NETLINK(在Linux系 ...

  2. 极客mysql16

    1.MySQL会为每个线程分配一个内存(sort_buffer)用于排序该内存大小为sort_buffer_size 1>如果排序的数据量小于sort_buffer_size,排序将会在内存中完 ...

  3. 05 . Vue前端交互,fetch,axios,以asyncawait方式调用接口使用及案例

    目标 /* 1. 说出什么是前后端交互模式 2. 说出Promise的相关概念和用法 3. 使用fetch进行接口调用 4. 使用axios进行接口调用 5. 使用asynnc/await方式调用接口 ...

  4. Mysql_笔记2018.1.29

    1.主要数据库 Oracle MySQL Sqlsever 微软 MongoDB (非关系型数据库) 2.MySql 专业词语 1.数据库:一些关联表的集合 2.数据表:表示数据的矩阵 3.列:同ex ...

  5. CSS属性(背景属性)

    1.背景属性 <!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset=" ...

  6. 死磕以太坊源码分析之Kademlia算法

    死磕以太坊源码分析之Kademlia算法 KAD 算法概述 Kademlia是一种点对点分布式哈希表(DHT),它在容易出错的环境中也具有可证明的一致性和性能.使用一种基于异或指标的拓扑结构来路由查询 ...

  7. 循序渐进VUE+Element 前端应用开发(28)--- 附件内容的管理

    在我们很多模块里面,都需要使用到一些诸如图片.Excel文件.PDF文件等附件的管理,一般我们倾向于把它独立为一个公用的附件管理模块,这样可以有效的统一管理附件的信息.本篇随笔介绍附件内容的管理,包括 ...

  8. 思维导图软件iMindMap怎么使用

    人人都说,思维导图记忆法实用.可是,我们应该如何使用思维导图呢?又该如何从思维小白摇身一变成为逻辑大神呢?俗话说,心急吃不了热豆腐,让我们一步一步来,慢慢接触使用思维导图吧. 小编作为"过来 ...

  9. 在Mac上也能轻松拥有Windows应用程序的简便方法

    一般而言,如果我们想要在Windows的环境下下载一款软件那是件很方便的事情.只要我们登陆软件的官网进行下载即可.但是如果我们使用的是Mac OS系统,很多用户就会发现,许多软件会出现不兼容的情况. ...

  10. 25. K 个一组翻转链表

    给你一个链表,每 k 个节点一组进行翻转,请你返回翻转后的链表.k 是一个正整数,它的值小于或等于链表的长度.如果节点总数不是 k 的整数倍,那么请将最后剩余的节点保持原有顺序.示例 :给定这个链表: ...