【题解】CF1083C Max Mex
题意
给定一棵带权树,所有结点的点权是 \(0\) 到 \(n - 1\) 的排列。
每次操作可以:
-
交换两个结点的点权
-
询问树上任意路径权值 \(Mex\) 的最大值
\(n, q \leq 2 \times 10^5\)
思路
线段树。
线段树可以解决的不仅是区间问题,只要问题满足可合并性都能用线段树做。
求 \(Mex\) 相当于求一个最大的自然数 \(x\),使得点权为 \([0, x)\) 的点都在同一条路径上。
我们发现这个区间是连续的,可以很好地用线段树做。
假设现在需要知道 \([l, r]\) 的点权是否在同一路径上,令 \(m = \lfloor \frac{l + r}{2} \rfloor\),此时已经知道 \([l, m]\) 和 \((m, r]\) 的答案。
显然左右两半的区间都必须在同一路径上。
不妨令这两条路径分别是 \((p, q), (a, b)\)。如果存在一条最短的路径包含 \([l, r]\) 的所有点权,那么这条路径的端点只有可能在 \(p, q, a, b\) 中取。
显然 \(q, a\) 在路径 \((a, b)\) 上的充要条件是 \(dis(p, q) + dis(q, b) = dis(p, a) + dis(a, b) = dis(p, b)\),于是可以用欧拉序 + ST 表 \(O(1)\) 做。每次线段树合并的时候就只需要枚举端点即可。
修改直接在线段树上做。
查询可以用线段树二分。假设现在已经知道在同一路径上的点权值域 \(V\)。因为线段树二分是按照中序遍历区间,所以可以直接尝试将整个左半区间加入 \(V\) 然后相应递归,顺序是正确的。
代码
#include <cstdio>
#include <iostream>
using namespace std;
const int maxn = 2e5 + 5;
const int maxe = 2e5 + 5;
struct node
{
int to, nxt;
} edge[maxe];
int n, q;
int cnt, ans;
int a[maxn], pos[maxn];
int head[maxn], dfn[maxn], dep[maxn];
namespace ST
{
int len;
int lg[maxn << 1], f[maxn << 1][20];
int get_min(int a, int b) { return (dep[a] < dep[b] ? a : b); }
void init()
{
for (int i = 2; i <= len; i++) lg[i] = lg[i >> 1] + 1;
for (int j = 1; (1 << j) <= len; j++)
for (int i = 1; i + (1 << j) - 1 <= len; i++)
f[i][j] = get_min(f[i][j - 1], f[i + (1 << (j - 1))][j - 1]);
}
int query(int l, int r)
{
if (l > r) swap(l, r);
int k = lg[r - l + 1];
return get_min(f[l][k], f[r - (1 << k) + 1][k]);
}
}
int lca(int a, int b) { return ST::query(dfn[a], dfn[b]); }
int dis(int a, int b) { return dep[a] + dep[b] - 2 * dep[lca(a, b)]; }
bool check(int p, int q, int a, int b)
{
int d = dis(p, b);
return (dis(p, q) + dis(q, b) == d) && (dis(p, a) + dis(a, b) == d);
}
namespace SGT
{
struct node
{
int u, v;
} tree[maxn << 2], ret;
void push_up(node &cur, node &l, node &r)
{
if ((!l.u) || (!r.u)) cur.u = cur.v = 0;
else if (check(l.u, l.v, r.u, r.v)) cur.u = l.u, cur.v = r.v;
else if (check(l.v, l.u, r.u, r.v)) cur.u = l.v, cur.v = r.v;
else if (check(l.u, l.v, r.v, r.u)) cur.u = l.u, cur.v = r.u;
else if (check(l.v, r.v, l.u, r.u)) cur.u = l.v, cur.v = r.u;
else if (check(l.u, r.u, r.v, l.v)) cur.u = l.u, cur.v = l.v;
else if (check(r.u, l.u, l.v, r.v)) cur.u = r.u, cur.v = r.v;
else cur.u = cur.v = 0;
}
void build(int k, int l, int r)
{
if (l == r)
{
tree[k].u = tree[k].v = pos[l];
return;
}
int mid = (l + r) >> 1;
build(k << 1, l, mid);
build(k << 1 | 1, mid + 1, r);
push_up(tree[k], tree[k << 1], tree[k << 1 | 1]);
}
void update(int k, int l, int r, int p, int w)
{
if (l == r)
{
tree[k].u = tree[k].v = w;
return;
}
int mid = (l + r) >> 1;
if (p <= mid) update(k << 1, l, mid, p, w);
else update(k << 1 | 1, mid + 1, r, p, w);
push_up(tree[k], tree[k << 1], tree[k << 1 | 1]);
}
bool modify(node nd, int len)
{
if (!ans)
{
ans = len, ret = nd;
return true;
}
node tmp;
push_up(tmp, ret, nd);
if (!tmp.u) return false;
ans += len, ret = tmp;
return true;
}
void query(int k, int l, int r)
{
// printf("%d, %d, %d\n", l, r, k);
if (l == r)
{
modify(tree[k], 1);
return;
}
int mid = (l + r) >> 1;
if (tree[k].u && modify(tree[k], r - l + 1)) return;
// if ((!ans) && (tree[k << 1].u)) puts("disadiajd");
if (tree[k << 1].u && modify(tree[k << 1], mid - l + 1)) query(k << 1 | 1, mid + 1, r);
else query(k << 1, l, mid);
}
}
void add_edge(int u, int v)
{
cnt++;
edge[cnt].to = v;
edge[cnt].nxt = head[u];
head[u] = cnt;
}
void dfs(int u, int fa)
{
dep[u] = dep[fa] + 1;
ST::f[++ST::len][0] = u;
dfn[u] = ST::len;
for (int i = head[u]; i; i = edge[i].nxt)
{
int v = edge[i].to;
dfs(v, u);
ST::f[++ST::len][0] = u;
}
}
int main()
{
// freopen("CF1083C_0.in", "r", stdin);
scanf("%d", &n);
for (int i = 1; i <= n; i++)
{
scanf("%d", &a[i]), a[i]++;
pos[a[i]] = i;
}
for (int i = 2, f; i <= n; i++)
{
scanf("%d", &f);
add_edge(f, i);
}
cnt = 0;
dfs(1, 0);
ST::init();
SGT::build(1, 1, n);
scanf("%d", &q);
while (q--)
{
int opt;
scanf("%d", &opt);
if (opt == 1)
{
int u, v;
scanf("%d%d", &u, &v);
SGT::update(1, 1, n, a[v], u);
SGT::update(1, 1, n, a[u], v);
swap(a[u], a[v]);
}
else
{
ans = 0;
SGT::query(1, 1, n);
printf("%d\n", ans);
}
}
return 0;
}
/*
1, 6, 1
1, 3, 2
1, 2, 4
2, 2, 9
1, 6, 1
4, 6, 3
4, 5, 6
4, 4, 12
*/