【题解】 [SDOI2013]森林 主席树+启发式合并+二分+差分 Luogu3302

Legend

Link \(\textrm{to Luogu}\)

小 Z 有一片森林,含有 \(N\ (1 \le N \le 8\times 10 ^4)\) 个节点,每个节点上都有一个非负整数作为权值。初始的时候,森林中有 \(M\ (0 \le M < N)\) 条边。

小Z希望执行 \(T\ (0 \le T \le 8\times 10 ^4)\) 个操作,操作有两类:

  • Q x y k 查询点 \(x\) 到点 \(y\) 路径上所有的权值中,第 \(k\) 小的权值是多少。此操作保证点 \(x\) 和点 \(y\) 连通,同时这两个节点的路径上至少有 \(k\) 个点。
  • L x y 在点 \(x\) 和点 \(y\) 之间连接一条边。保证完成此操作后,仍然是一片森林。

强制在线

时空:\(\textrm{2s/512MB}\)

Editorial

好像还是比较显然的。

题目里有一档部分分叫“没有 \(\rm{L}\) 操作”,这个东西人人都会,直接主席树+树上差分+二分就没了。

你会发现所有的数据范围都特别小……

于是你会发现你只要每次合并两棵树的时候采用启发式合并就可以了。

同时要维护一个父亲的倍增数组。

这样子是 \(O(T \log^2 n)\) 的,可以通过。

空间同理,你甚至不需要写内存回收。

Code

今天写代码的时候把 \(\rm{LCA}\) 求错了,引以为戒。

#include <bits/stdc++.h>

#define __FILE(x)\
	freopen(#x".in" ,"r" ,stdin);\
	freopen(#x".out" ,"w" ,stdout)

const int MX = 8e4 + 23;

int read(){
	char k = getchar(); int x = 0;
	while(k < '0' || k > '9') k = getchar();
	while(k >= '0' && k <= '9')
		x = x * 10 + k - '0' ,k = getchar();
	return x;
}

int head[MX] ,tot;
struct edge{
	int node ,next;
}h[MX << 1];
void addedge(int u ,int v ,int flg = 1){
	h[++tot] = (edge){v ,head[u]} ,head[u] = tot;
	if(flg) addedge(v ,u ,false);
}

int a[MX] ,b[MX];

int toppp;
struct node{
	int l ,r ,s ,bl;
	node *lch ,*rch;
}POOL[MX * 17 * 17 + 2 * MX] ,*root[MX];

node *newn(){return &POOL[++toppp];}
void pushup(node *x){x->s = x->lch->s + x->rch->s;}

node *build(int l ,int r){
	node *x = newn();
	x->l = l ,x->r = r ,x->s = x->bl = 0;
	if(l == r) x->lch = x->rch = nullptr;
	else{int mid = (l + r) >> 1;
		x->lch = build(l ,mid);
		x->rch = build(mid + 1 ,r);
		pushup(x);
	}return x;
}

node *extend(node *org ,int pos ,int del ,int bl){
	node *x = newn(); *x = *org;
	x->bl = bl;
	if(x->l == x->r){x->s += del;}
	else{
		if(pos <= x->lch->r) x->lch = extend(org->lch ,pos ,del ,bl);
		else x->rch = extend(org->rch ,pos ,del ,bl);
		pushup(x);
	}return x;
}

int Query(node *x ,node *y ,node *lca ,node *lcaf ,int k){
	if(x->l == x->r) return x->l;
	int left = x->lch->s + y->lch->s - lca->lch->s - lcaf->lch->s;
	if(left >= k) return Query(x->lch ,y->lch ,lca->lch ,lcaf->lch ,k);
	return Query(x->rch ,y->rch ,lca->rch ,lcaf->rch ,k - left);
}

int rt[MX] ,sz[MX] ,fa[17][MX] ,dep[MX];
void dfsBuild(int x ,int f){
	rt[x] = f ? rt[f] : x;
	dep[x] = dep[f] + 1;
	++sz[rt[x]];
	fa[0][x] = f;
	for(int i = 1 ; i <= 16 ; ++i)
		fa[i][x] = fa[i - 1][fa[i - 1][x]];
	root[x] = extend(root[f] ,a[x] ,1 ,x);
	for(int i = head[x] ,d ; i ; i = h[i].next){
		if((d = h[i].node) == f) continue;
		dfsBuild(d ,x);
	}
}

int LCA(int a ,int b){
	if(dep[a] < dep[b]) std::swap(a ,b);
	for(int i = 16 ; ~i ; --i)
		if(dep[fa[i][a]] >= dep[b]) a = fa[i][a];
	if(a == b) return a;
	for(int i = 16 ; ~i ; --i)
		if(fa[i][a] != fa[i][b])
			a = fa[i][a] ,b = fa[i][b];
	return fa[0][a];
}

int main(){
	__FILE([SDOI2013]森林);
	read();
	int n = read() ,m = read() ,t = read();
	for(int i = 1 ; i <= n ; ++i) a[i] = b[i] = read();
	std::sort(b + 1 ,b + 1 + n);
	for(int i = 1 ; i <= n ; ++i) a[i] = std::lower_bound(b + 1 , b + 1 + n ,a[i]) - b;
	for(int i = 1 ,u ,v ; i <= m ; ++i){
		u = read() ,v = read();
		addedge(u ,v);
	}
	root[0] = build(1 ,n);
	for(int i = 1 ; i <= n ; ++i){
		if(!rt[i]) dfsBuild(i ,0);
	}

	int la = 0;
	while(t--){
		char str[4];
		scanf("%s" ,str);
		if(str[0] == 'Q'){
			int x = read() ^ la ,y = read() ^ la ,k = read() ^ la;
			int lca = LCA(x ,y);
			// fprintf(stderr ,"> query %d %d %d lca = %d\n" ,x ,y ,k ,lca);
			printf("%d\n" ,la = b[Query(root[x] ,root[y] ,root[lca]
						,root[fa[0][lca]] ,k)]);
		}
		else{
			int x = read() ^ la ,y = read() ^ la;
			// fprintf(stderr ,"> LINK %d %d\n" ,x ,y);
			if(sz[rt[x]] < sz[rt[y]]) std::swap(x ,y);
			dfsBuild(y ,x);
			addedge(x ,y);
		}
	}
	return 0;
}
原文地址:https://www.cnblogs.com/imakf/p/13685817.html