【线段树】【CF1083C】 Max Mex

Description

给定一棵有 (n) 个点的树,每个节点有点权。所有的点权构成了一个 (0~sim~n - 1) 的排列。有 (q) 次操作,每次操作 (1) 为交换两个点的点权,操作 (2) 为查询 (Mex(l)) 值最大的 (Mex(l)) 值,其中 (l) 是树上的一条路径。定义一条路径 (l)(Mex)(Mex(l)) 为这条路径上最小的没有出现过的自然数

Input

第一行是一个整数 (n) 代表点数

下面一行是 (n) 个数字,代表每个点的点权,保证是一个 (0~sim n - 1) 的排列

下面一行有 (n - 1) 个数字,第 (i) 个数字代表钦定 (1) 为根时第 (i + 1) 个点的父节点

下面一行是一个整数 (q),代表操作个数

下面 (q) 行,每行一组操作。

如果该行第一个数字为 (1),则后面有两个数字 (x,y),代表交换 (x,y) 的点权

否则该行有且仅有一个数字 (2),代表一次查询

Output

对每次查询输出一行一个整数作为答案

Hint

(2~leq~n~leq~2~ imes~10^5~,~1~leq~q~leq~2~ imes~10^5)

Solution

一道线段树维护图上信息的好题。

记得以前在某学堂学习时,PKU的HJC老师说线段树出现在高端算法竞赛中,除了扔上来一个序列维护一堆东西以外,如果可以用线段树解决,那么原因一定是这道题目的信息具有 可合并性。现在想想这大概就是一个比较恰当的例题了。

我们考虑这道题目,每次询问可以转化成查询一个最小的 (k) 使得 ([0,k)) 都出现在同一条链上。这个 (k) 显然是满足二分性质的,于是我们只需要一个能够维护这树节点权值前缀的连通性的数据结构了。考虑如果相邻的两段权值区间的点可以被合并为一条链,那么这段权值区间并显然是合法的。我们考虑对每段区间维护过区间内所有值的链的两个端点。注意这条链必须包含区间中的所有权值但是不一定仅包含这些权值。例如对于区间 ([1,2]),这条链包含 ({1,2,4}) 也是合法的。

考虑合并时大区间合法当且仅当从两个链的四个端点中选两个做新的端点,剩下两个点都在链上即可。参考 这道题 我们可以得到判断一个点是否在一条链上的方法,即:

记两端点为 (u,v),需要判定的点为 (x)(LCA(u,v)~=~y),则有:

(x) 在链 ((u,v))(Leftrightarrow) $$((LCA(u,x) == x~lor~LCA(v,x) == x)~land LCA(x,y) == y)$$

于是枚举一下两个端点,判定一下即可。

发现在合并时我们会大量用到求LCA的操作,倍增显然不够优秀,于是我们使用ST预处理从而做到 (O(1)) 查询。这样pushup的复杂度就被我们降到了 (O(w)),其中 (w) 为枚举端点的情况数,即 (C_4^2~=~6)。所以修改的复杂度为 (O(w log n))

考虑查询时,直接在线段树上二分,维护前缀链的连通性即可做到 (O(w log n))

于是总复杂度 (O((n + q)~w log n)),其中 (w~=~6),可以通过本题

Code

#include <cmath>
#include <cstdio>
#include <vector>
#include <algorithm>
#ifdef ONLINE_JUDGE
#define putchar(o)
puts("I am a cheater!")
#define freopen(a, b, c)
#endif
#define rg register
#define ci const int
#define cl const long long

typedef long long int ll;

namespace IPT {
	const int L = 1000000;
	char buf[L], *front=buf, *end=buf;
	char GetChar() {
		if (front == end) {
			end = buf + fread(front = buf, 1, L, stdin);
			if (front == end) return -1;
		}
		return *(front++);
	}
}

template <typename T>
inline void qr(T &x) {
	rg char ch = IPT::GetChar(), lst = ' ';
	while ((ch > '9') || (ch < '0')) lst = ch, ch=IPT::GetChar();
	while ((ch >= '0') && (ch <= '9')) x = (x << 1) + (x << 3) + (ch ^ 48), ch = IPT::GetChar();
	if (lst == '-') x = -x;
}

template <typename T>
inline void ReadDb(T &x) {
	rg char ch = IPT::GetChar(), lst = ' ';
	while ((ch > '9') || (ch < '0')) lst = ch, ch = IPT::GetChar();
	while ((ch >= '0') && (ch <= '9')) x = x * 10 + (ch ^ 48), ch = IPT::GetChar();
	if (ch == '.') {
		ch = IPT::GetChar();
		double base = 1;
		while ((ch >= '0') && (ch <= '9')) x += (ch ^ 48) * ((base *= 0.1)), ch = IPT::GetChar();
	}
	if (lst == '-') x = -x;
}

namespace OPT {
	char buf[120];
}

template <typename T>
inline void qw(T x, const char aft, const bool pt) {
	if (x < 0) {x = -x, putchar('-');}
	rg int top=0;
	do {OPT::buf[++top] = char(x % 10 + '0');} while (x /= 10);
	while (top) putchar(OPT::buf[top--]);
	if (pt) putchar(aft);
}

const int maxn = 2000010;
const int maxm = 4000010;

int n, q, vistime, t;
int MU[maxn], fa[maxn], sz[maxn], dfn[maxm], pre[maxn], ST[30][maxn], deepth[maxn], rmp[maxn], dre[maxn];
std::vector<int> son[maxn];

struct Tree;

void dfs(ci);
void Make_ST();
int ask(Tree*);
void buildpool();
int cmp(ci&, ci&);
int Get_Lca(ci, ci);
void update(Tree*, ci);
bool Is_Lca(ci, ci, ci);
void build(Tree*, ci, ci);
void check(Tree*, Tree*, Tree*);

struct Tree {
	Tree *ls, *rs;
	int l, r;
	int v[2];
	
	inline void pushup() {
		if (!this->rs) {this->v[0] = this->ls->v[0]; this->v[1] = this->ls->v[1];}
		else if (!this->ls) {this->v[0] = this->rs->v[0]; this->v[1] = this->rs->v[1];}
		else if (!(~this->ls->v[0])) {
			this->v[0] = this->v[1] = -1;
		} else if (!(this->rs->v[0])) {
			this->v[0] = this->v[1] = -1;
		} else {
			check(this->ls, this->rs, this);
		}
	}
};
Tree *pool[maxm], qwq[maxm], *rot, pree;
int top;

int main() {
	freopen("1.in", "r", stdin);
	qr(n);
	for (rg int i = 1; i <= n; ++i) {qr(MU[i]); ++MU[i];}
	for (int i = 2; i <= n; ++i) {
		qr(fa[i]); son[fa[i]].push_back(i); ++sz[fa[i]]; ++dre[fa[i]]; ++dre[i];
	}
	bool _flag = true;
	for (rg int i = 1; i <= n; ++i) _flag &= dre[i] <= 2;
	if (_flag) {
		qr(q);
		int a;
		while (q--) {
			a = 0; qr(a);
			if (a == 2) {
				qw(n, '
', true);
			} else {
				qr(a); qr(a);
			}
		}
		return 0;
	}
	dfs(1);
	Make_ST();
	for (rg int i = 1; i <= n; ++i) rmp[MU[i]] = i;
	buildpool();
	build(rot, 1, n);
	qr(q); int a, b, c;
	while (q--) {
		a = 0; qr(a);
		if (a == 2) {
			pree.v[1] = pree.v[0] = 0; qw(ask(rot) - 1, '
', true);
		}
		else {
			b = c = 0; qr(b); qr(c);
			std::swap(rmp[MU[b]], rmp[MU[c]]); std::swap(MU[b], MU[c]);
			update(rot, MU[b]); update(rot, MU[c]);
		}
	}
	return 0;
}

void dfs(ci u) {
	dfn [pre[u] = ++vistime] = u;
	for (int i = 0; i < sz[u]; ++i) {
		deepth[son[u][i]] = deepth[u] + 1;
		dfs(son[u][i]); dfn[++vistime] = u;
	}
}

void Make_ST() {
	int dn = n << 1;
	t = log2(dn) + 1;
	for (rg int i = 1; i < dn; ++i) ST[0][i] = dfn[i];
	for (rg int i = 1; i <= t; ++i) {
		rg int di = i - 1;
		for (rg int l = 1; l < dn; ++l) {
			int r= l + (1 << i) - 1;
			if (r >= dn) break;
			ST[i][l] = cmp(ST[di][l], ST[di][l + (1 << di)]);
		}
	}
}

int cmp(ci &_a, ci &_b) {
	if (deepth[_a] < deepth[_b]) return _a;
	return _b;
}

void buildpool() {
	for (rg int i = 0; i < maxm; ++i) pool[i] = qwq + i;
	rot = pool[maxm - 1]; top = maxm - 2;
}

void build(Tree *u, ci l, ci r) {
	if ((u->l = l) == (u->r = r)) {u->v[1] = u->v[0] = rmp[l]; return;}
	int mid = (l + r) >> 1;
	if (l <= mid) build(u->ls = pool[top--], l, mid);
	if (mid < r) build(u->rs = pool[top--], mid + 1, r);
	u->pushup();
}

int Get_Lca(ci u, ci v) {
	int l = pre[u], r = pre[v];
	if (l > r) std::swap(l, r);
	int len = r - l + 1, T = log2(len);
	return cmp(ST[T][l], ST[T][r - (1 << T) + 1]);
}

bool Is_Lca(ci u, ci v, ci x) {
	int _tmp = Get_Lca(u, v);
	return (((Get_Lca(u, x) == x) || (Get_Lca(v, x) == x)) && (Get_Lca(_tmp, x) == _tmp));
}

#define doit 
	if (Is_Lca(v1, u1, x) && (Is_Lca(v1, u1, y))) {
			u->v[0] = u1; u->v[1] = v1;
			return;
	}
void check(Tree *ls, Tree *rs, Tree *u) {
	if (ls->v[0] == 0) { *u = *rs; return;}
	int u1, v1, x, y;
	{
		u1 = ls->v[0]; v1 = ls->v[1]; x = rs->v[0]; y = rs->v[1];
		doit;
	}
	{
		u1 = rs->v[0]; v1 = ls->v[0]; x = rs->v[1]; y = ls->v[1];
		doit;
	}
	{
		u1 = rs->v[1]; v1 = ls->v[1]; x = rs->v[0]; y = ls->v[0];
		doit;
	}
	{
		u1 = rs->v[1]; v1 = ls->v[0]; x = rs->v[0]; y = ls->v[1];
		doit;
	}
	{
		u1 = rs->v[0]; v1 = ls->v[1]; x = rs->v[1]; y = ls->v[0];
		doit;
	}
	{
		u1 = rs->v[0]; v1 = rs->v[1]; x = ls->v[0]; y = ls->v[1];
		doit;
	}
	u->v[0] = u->v[1] = -1;
}
#undef doit

void update(Tree *u, ci pos) {
	if ((u->l > pos) || (u->r < pos)) return;
	if (u->l == u->r) {u->v[0] = u->v[1] = rmp[pos]; return;}
	if (u->ls) update(u->ls, pos);
	if (u->rs) update(u->rs, pos);
	u->pushup();
}

int ask(Tree *u) {
	Tree _tmp;
	if ((u->ls)) {
		check(&pree, u->ls, &_tmp);
		if (~_tmp.v[0]) {
			pree = _tmp; return u->rs ? ask(u->rs) : u->r;
		} else {
			return ask(u->ls);
		}
	} else if (u->rs) return ask(u->rs);
	else return u->r;
}

Summary

判断一个点在一条链上的方法为:它和链的其中一个端点的LCA是他自己且它和这条链顶端的节点的LCA是顶端节点。

当LCA查询极多且难以离线处理时,考虑使用ST预处理欧拉遍历序做到 (O(1)) 查询

注意ST存储欧拉遍历序需要开 (2n) 的空间

原文地址:https://www.cnblogs.com/yifusuyi/p/10241061.html