[CF1083C Round#526 Div.1]Max Mex——[LCA+线段树合并]

【原题】
在这里插入图片描述
【题目翻译】

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

【输入格式】

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

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

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

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

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

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

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

S a m p l e    I n p u t Sample~~Input Sample  Input

6
5 2 1 4 3 0
1 1 1 3 3
9
2
1 5 3
2
1 6 1
2
1 4 2
2
1 1 6
2

S a m p l e    O u t p u t Sample~~Output Sample  Output

3
2
4
4
2

【题意分析】

部分参考自ww3113306

题目翻译成人话:两种操作:交换点权/查询整棵树中的所有路径上最小的未出现过的自然数

交换点权好处理,链上最小的未出现过的自然数怎么做呢?

如果x是答案,那么链上0~x-1都必须出现

于是用线段树节点的左右值域表示是否存在这样的路径,左儿子右儿子存树上dfs序编号(如果有)

因为权值唯一,可以在dfs的时候保存数据以实现编号<=>权值互化

考虑合并左右儿子 [ L , m i d ] [L,mid] [L,mid] [ m i d + 1 , R ] [mid+1,R] [mid+1,R](注意这边写出的是值,不是编号,编号存在线段树里)

四个值就有四个节点,我们从四个点中选两个,如果另外两个在这两个构成的路径上,不就大功告成了?

问题转化为:判断 x x x是否在 ( u , v ) (u,v) (u,v)组成的树上路径上

y = L C A ( u , v ) y=LCA(u,v) y=LCA(u,v)

画图理解:必须满足 L C A ( u , x ) = x LCA(u,x)=x LCA(u,x)=x或者 L C A ( v , x ) = x LCA(v,x)=x LCA(v,x)=x,并且 L C A ( x , y ) LCA(x,y) LCA(x,y)一定等于 y y y

显然本题要用到大量LCA,ST显然是最好的,但这玩意很容易写挂导致调很久

实测此题树剖求LCA跑得飞快甚至比ST快,倍增就可能跑不过了

查询操作可以在外面二分,但这样会t

我们可以在线段树上直接二分,这样就去掉了一个log,可以通过此题。

细节比较多,线段树内部我选择了pair

#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <cctype>
#include <cmath>
#include <algorithm>
#define MAXN 400100
#define mp make_pair
#define rep(x,a,b) for (register int x = a; x <= b; x++)
#define cross(x,a) for (register int x = head[a]; x; x = edge[x].next)
using namespace std;
typedef pair <int, int> pii;

struct fls {
	int to, next;
}edge[MAXN << 1];

int head[MAXN << 1], v[MAXN], s[MAXN], id[MAXN], rk[MAXN], ST[MAXN][21];
int depth[MAXN], q[20], size[MAXN], son[MAXN], top[MAXN], father[MAXN], cnt, dfn, n;
pii tree[MAXN << 2], o;

inline void connect (int u, int v) {edge[++cnt].to = v, edge[cnt].next = head[u], head[u] = cnt;}
inline int f (int x, int y) {return depth[x] > depth[y] ? y : x;}
inline int read () {
	register int s = 0, w = 1;
	register char ch = getchar ();
	while (! isdigit (ch)) {if (ch == '-') w = -1; ch = getchar ();}
	while (isdigit (ch)) {s = (s << 3) + (s << 1) + (ch ^ 48); ch = getchar ();}
	return s * w;
}

void DFS1 (int now, int fa, int d) {
	father[now] = fa; depth[now] = d; size[now] = 1;
	int maxson = -1;
	cross (i, now) {
		int v = edge[i].to;
		if (v == fa)continue;
		DFS1 (v, now, d+1);
		size[now] += size[v];
		if (size[v] > maxson) {
			maxson = size[v];
			son[now] = v;
		}
	}
}

void DFS2 (int now, int top_heavy) {
	top[now] = top_heavy;
	if (! son[now])return;
	DFS2 (son[now], top_heavy);
	cross (i, now) {
		int v = edge[i].to;
		if (v != father[now] && v != son[now])DFS2 (v,v);
	}
}

inline int LCA (int x, int y) {
	while (top[x] != top[y])
		(depth[top[x]] >= depth[top[y]])
			? x = father[top[x]]
			: y = father[top[y]];
	return (depth[x] < depth[y]) ? x : y;
}

namespace segtree {
	
	inline bool islegal (int x, int u, int v) {
		int tmp = LCA (u, v);
		return (((LCA (x, u) == x) || (LCA (x, v) == x)) && (LCA (tmp, x) == tmp));
	}
	
	inline pii merge (pii L, pii R) {
		if (! L.first || ! R.first) return mp (0, 0);
		q[1] = L.first, q[2] = L.second, q[3] = R.first, q[4] = R.second;
		rep (i, 1, 4) rep (j, i + 1, 4) {
			if (q[i] == q[j]) continue; bool flag = 0;
			rep (k, 1, 4) if (i != k && j != k && ! islegal (q[k], q[i], q[j])) flag = 1;
			if (! flag) return mp (q[i], q[j]);
		}
		return mp (0, 0);
	}
	
	void build (int now, int L, int R) {
		if (L == R) {
			tree[now].first = tree[now].second = s[L];
			return;
		}
		int mid = L + R >> 1;
		build (now << 1, L, mid);
		build (now << 1 | 1, mid + 1, R);
		tree[now] = merge (tree[now << 1], tree[now << 1 | 1]);
	}
	
	void update (int now, int L, int R, int k) {
		if (L == R) {
			tree[now].first = tree[now].second = k;
			return;
		}
		int mid = L + R >> 1;
		if (v[k] <= mid) update (now << 1, L, mid, k);
		else update (now << 1 | 1, mid + 1, R, k);
		tree[now] = merge (tree[now << 1], tree[now << 1 | 1]);
	}
	
	int query (int now, int L, int R) {
		if (L == R) {
			pii tmp = (o.first) ? merge (o, tree[now]) : tree[now];
			if (tmp.first) {
				o = tmp; return 1;
			}
			return 0;
		}
		int mid = L + R >> 1;
        if (! tree[now << 1].first) return query (now << 1, L, mid);
        else {
			pii tmp = (o.first) ? merge (o, tree[now << 1]) : tree[now << 1];
            if (! tmp.first) return query (now << 1, L, mid);
            else {
				o = tmp;
				return mid - L + 1 + query (now << 1 | 1, mid + 1, R);
			}
        }
	}
	
}using namespace segtree;

int main () {
	n = read ();
	rep (i, 1, n) v[i] = read () + 1, s[v[i]] = i;
	rep (i, 2, n) connect (read (), i);
	DFS1 (1, 0, 1), DFS2 (1, 1), build (1, 1, n);
	int T = read ();
	rep (I, 1, T) {
		int opt = read ();
		if (opt == 1) {
			int x = read (), y = read (); swap (v[x], v[y]);
			update (1, 1, n, x), update (1, 1, n, y);
		}
		if (opt == 2) {
			o = mp (0, 0);
			printf ("%d\n", query (1, 1, n));
		}
	}
	return 0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值