CSP-S 模拟 迷雾华光 (树分块)(虚树)(主席树)(树上莫队)

题意:给一棵树,每个节点有 3 个权值,问路径众数,次数相同以最小的为众数
n ≤ 8 e 4 , q ≤ 1 e 5 , 512 M B , 10 s n\le 8e4,q\le1e5,512MB,10s n8e4,q1e5,512MB,10s


首先 3 个权值就是故意来累加你常数的东西,既然开了 10 s 10s 10s,就会猜到肯定是根号算法


离线算法
首先考虑离线就是一个树上莫队,但是由于树上莫队按 d f s dfs dfs 序到序列上的时候出现第二次时需要删除,有些头疼,考虑维护一个数据结构维护有没有出现次数为 x x x 的数,当有一个的时候,在这个数据结构上 + 1 +1 +1,然后还要对每一个出现次数为 x x x 的数维护一个最小,有要支持删除,只好上 s e t set set

如果用树分块而不在序列上分块的话用回滚莫队就可以优化到 n n n\sqrt n nn


在线算法
我们在树上选 S S S 个关键点,让任意一个点到关键点的距离不超过 n S \frac{n}{S} Sn
有一个巧妙的构造方法是从叶子开始,每个点求向下的最长链,如果遇到关键点就在那里把 d i s dis dis 设成 0
如果最长链的长度超过了我们规定的 S I Z E SIZE SIZE ,那么就把它置为关键点

查询的时候,暴力跳 f a fa fa,跳到两个关键点,预处理任意两个关键点的答案中间就可以 O ( 1 ) O(1) O(1) 知道
然后考虑两边零散的点,需要知道它们在链上的出现次数,方法有如下几种:

算法1:
暴力上主席树,这样复杂度是 O ( q ∗ l o g ( n ) ∗ n ) O(q*log(n)*\sqrt n) O(qlog(n)n ),如果改一下块的大小的话可以做到 n l o g ( n ) \sqrt {nlog(n)} nlog(n)

算法2:
预处理关键点到根的任意一个值的前缀和
找到两个关键点离 lca 最近的关键点,显然两边单独的路径可以 O ( 1 ) O(1) O(1) 查询
然后两边剩下的和 lca 拼起来的长度显然不超过 n \sqrt n n ,暴力跳这 3 段路径即可
复杂度 O ( n ) O(\sqrt n) O(n )

算法3:
用关键点建出虚树,把虚树上的点都当成关键点,同样预处理到根的和
查询路径的时候,一个点如果是关键点,简单的树上差分就可以 O ( 1 ) O(1) O(1)
如果不是关键点,那么它只可能有一个儿子的子树中有关键点,我们预处理可以找出那个点
再判断能不能用那个关键点即可,复杂度 O ( n ) O(\sqrt n) O(n )
也可以把每一个权值 v a l val val 嗲出来建虚树,本质上差不多


预处理:
需要预处理两个关键点的答案
对每个关键点 d f s dfs dfs 一遍,退栈的时候要更新最大值,头疼
发现如果只加数是可以很方便地更新众数,而回退只需要推桶
在进每个点的时候记录一个 p r e pre pre,出去的时候退桶,把众数改成 p r e pre pre 即可
其实就是回滚莫队的思想,不支持删除我们就撤销

预处理到根路径的前缀和,dfs 一遍即可


查询的细节
对两个点能不能找到关键点讨论

  1. 两个都没有,暴力跳
  2. 一个有,在有的那边继续往上跳,跳到离 l c a lca lca 最近的,
    暴力跳令一边和离 l c a lca lca最近的关键点到 l c a lca lca的那一部分,容易发现不管怎么跳都是 n \sqrt n n
  3. 两个都有,直接插那两个的答案即可

建议使用只带一个 n \sqrt n n , 而不是 n ∗ l o g ( n ) \sqrt {n*log(n)} nlog(n) ,不然会像这样
在这里插入图片描述
由于用的主席树,为了卡常,肯定让每个数只在路径只查一次,记录一个 v i s vis vis 再开一个栈
作为过后暴力退回去即可


学习了一下另一种树分块的实现,就是处理链的信息的找关键点的方法
然后在和大家讨论的过程中收获了一下分块的优秀及乱搞
练习打 8 k + 8k+ 8k+ 的代码能力
只带一个 n \sqrt n n 的以后再补


#include<bits/stdc++.h>
#define cs const
using namespace std;
namespace IO{
	cs int Rlen = 1 << 22 | 1;
	char buf[Rlen], *p1, *p2;
	char gc(){
		return (p1 == p2) && (p2 = (p1 = buf) + fread(buf, 1, Rlen, stdin), p1 == p2) ? EOF : *p1++;
	}
	int read(){
		int x = 0, f = 1; char ch = 0;
		while(!isdigit(ch)){ ch = gc(); if(ch == '-') f = -1; }
		while(isdigit(ch)) x = (x + (x << 2) << 1) + (ch - '0'), ch = gc();
		return x * f;
	}
} using namespace IO;
cs int N = 8e4 + 5, M = 700, SIZ = 120;
int first[N], nxt[N<<1], to[N<<1], tot;
void add(int x, int y){nxt[++tot] = first[x], first[x] = tot, to[tot] = y; }
int Type, n, m, a[N][3], xorsum;
int top[N], son[N], siz[N], in[N], fa[N], dep[N], sign;
#define mid ((l+r)>>1)
int rt[N], ls[N * 60], rs[N * 60], Sum[N * 60], node;
void ins(int &x, int las, int l, int r, int p){
	x = ++node; ls[x] = ls[las]; rs[x] = rs[las]; Sum[x] = Sum[las] + 1;
	if(l == r) return;
	if(p <= mid) ins(ls[x], ls[las], l, mid, p);
	else ins(rs[x], rs[las], mid+1, r, p); 
}
int ask(int a, int b, int c, int d, int l, int r, int p){
	if(l == r) return Sum[a] + Sum[b] - Sum[c] - Sum[d];
	if(p <= mid) return ask(ls[a], ls[b], ls[c], ls[d], l, mid, p);
	else return ask(rs[a], rs[b], rs[c], rs[d], mid+1, r, p);
}
#undef mid
void dfs(int u, int f){
	siz[u] = 1; rt[u] = rt[f];
	ins(rt[u], rt[u], 1, n, a[u][0]);
	ins(rt[u], rt[u], 1, n, a[u][1]);
	ins(rt[u], rt[u], 1, n, a[u][2]);
	for(int i = first[u]; i; i= nxt[i]){
		int t = to[i]; if(t == f) continue;
		dep[t] = dep[u] + 1; fa[t] = u;
		dfs(t, u); siz[u] += siz[t]; 
		if(siz[son[u]] < siz[t]) son[u] = t;
	}
}
void dfs2(int u, int tp){
	top[u] = tp; in[u] = ++sign; 
	if(son[u]) dfs2(son[u], tp);
	for(int i = first[u]; i; i = nxt[i]){
		int t = to[i]; if(t == son[u] || t == fa[u]) continue; dfs2(t, t);
	}
}
int lca(int x, int y){
	while(top[x]^top[y]){ if(dep[top[x]]<dep[top[y]]) swap(x,y); x = fa[top[x]]; } 
	return dep[x] < dep[y] ? x : y; 
}

int bin[N]; int blk[N], ct; 
vector<int> key;
int sum[N][M], mx[M][M], num;
int upper[N]; // 关键点上方的点  
void prework(int rt, int u, int fa){
	int pre = num;
	bin[a[u][0]]++;
	bin[a[u][1]]++;
	bin[a[u][2]]++;
	if(bin[a[u][0]] > bin[num] || (bin[a[u][0]] == bin[num] && a[u][0] < num)) num = a[u][0];
	if(bin[a[u][1]] > bin[num] || (bin[a[u][1]] == bin[num] && a[u][1] < num)) num = a[u][1];
	if(bin[a[u][2]] > bin[num] || (bin[a[u][2]] == bin[num] && a[u][2] < num)) num = a[u][2];
	if(blk[u]) mx[rt][blk[u]] = num;
	for(int i = first[u]; i; i = nxt[i]){
		int t = to[i]; if(t == fa) continue;
		prework(rt, t, u); 
	}
	num = pre;
	bin[a[u][0]]--;
	bin[a[u][1]]--;
	bin[a[u][2]]--;
}
void workupper(int u, int fa, int pos){
	upper[u] = pos; 
	for(int i = first[u]; i; i = nxt[i]){
		int t = to[i]; if(t == fa) continue;
		blk[u] ? workupper(t, u, u) : workupper(t, u, pos);
	}
}
int qsz(int x, int u, int v, int lca){
	return ask(rt[u], rt[v], rt[lca], rt[fa[lca]], 1, n, x);
}
bool already[N]; int st[N];
void solve1(int nx, int ny, int u, int v, int l){
	int U = u, V = v;
	int num = mx[blk[nx]][blk[ny]], tim = qsz(num, u, v, l); 
	int tp = 0; st[++tp] = num; already[num] = true;
	while(u^nx){
		for(int i = 0; i < 3; i++){
			if(already[a[u][i]]) continue; already[a[u][i]] = true;
			st[++tp] = a[u][i];
			int ret = qsz(a[u][i], U, V, l);
			if(ret > tim || (ret == tim && a[u][i] < num)) num = a[u][i], tim = ret;
		} u = fa[u];
	} 
	while(v^ny){
		for(int i = 0; i < 3; i++){
			if(already[a[v][i]]) continue; already[a[v][i]] = true;
			st[++tp] = a[v][i];
			int ret = qsz(a[v][i], U, V, l);
			if(ret > tim || (ret == tim && a[v][i] < num)) num = a[v][i], tim = ret;
		} v = fa[v];
	} cout << tim << " " << num << '\n'; xorsum ^= tim; xorsum ^= num;
	while(tp) already[st[tp--]] = false;
}
void solve2(int nx, int u, int v, int l){
	int U = u, V = v;
	int ny = nx;
	while(dep[upper[ny]] >= dep[l]) ny = upper[ny];
	int num = mx[blk[nx]][blk[ny]], tim = qsz(num, u, v, l);
	int tp = 0; st[++tp] = num; already[num] = true;
	while(u^nx){
		for(int i = 0; i < 3; i++){
			if(already[a[u][i]]) continue; 
			already[a[u][i]] = true;
			st[++tp] = a[u][i];
			int ret = qsz(a[u][i], U, V, l);
			if(ret > tim || (ret == tim && a[u][i] < num)) num = a[u][i], tim = ret;
		} u = fa[u];
	} 
	u = ny;
	while(u^l){
		for(int i = 0; i < 3; i++){
			if(already[a[u][i]]) continue; 
			already[a[u][i]] = true;
			st[++tp] = a[u][i];
			int ret = qsz(a[u][i], U, V, l);
			if(ret > tim || (ret == tim && a[u][i] < num)) num = a[u][i], tim = ret;
		} u = fa[u];	
	}
	while(v^l){
		for(int i = 0; i < 3; i++){
			if(already[a[v][i]]) continue; already[a[v][i]] = true;
			st[++tp] = a[v][i];
			int ret = qsz(a[v][i], U, V, l);
			if(ret > tim || (ret == tim && a[v][i] < num)) num = a[v][i], tim = ret;
		} v = fa[v];
	}
	for(int i = 0; i < 3; i++){
		if(already[a[v][i]]) continue; already[a[v][i]] = true;	
		st[++tp] = a[v][i];		
		int ret = qsz(a[v][i], U, V, l);
		if(ret > tim || (ret == tim && a[v][i] < num)) num = a[v][i], tim = ret;
	}
	cout << tim << " " << num << '\n'; xorsum ^= tim; xorsum ^= num;
	while(tp) already[st[tp--]] = false;
}
void solve3(int u, int v, int l){
	int U = u, V = v;
	int num = 0, tim = 0;
	int tp = 0; 
	while(u^l){
		for(int i = 0; i < 3; i++){
			if(already[a[u][i]]) continue; already[a[u][i]] = true;
			st[++tp] = a[u][i];
			int ret = qsz(a[u][i], U, V, l);
			if(ret > tim || (ret == tim && a[u][i] < num)) num = a[u][i], tim = ret;
		} u = fa[u];
	} 
	while(v^l){
		for(int i = 0; i < 3; i++){
			if(already[a[v][i]]) continue; already[a[v][i]] = true;
			st[++tp] = a[v][i];
			int ret = qsz(a[v][i], U, V, l);
			if(ret > tim || (ret == tim && a[v][i] < num)) num = a[v][i], tim = ret;
		} v = fa[v];
	} 
	for(int i = 0; i < 3; i++){
		if(already[a[u][i]]) continue; already[a[u][i]] = true;	
		st[++tp] = a[u][i];	
		int ret = qsz(a[u][i], U, V, l);
		if(ret > tim || (ret == tim && a[u][i] < num)) num = a[u][i], tim = ret;
	}
	cout << tim << " " << num << '\n'; xorsum ^= tim; xorsum ^= num; 
	while(tp) already[st[tp--]] = false;
}
void query(int u, int v){
	int l = lca(u, v);
	int nx = u, ny = v;
	while(nx ^ l){ if(blk[nx]) break; nx = fa[nx]; }
	while(ny ^ l){ if(blk[ny]) break; ny = fa[ny]; }
	if(blk[nx] && blk[ny]) solve1(nx, ny, u, v, l);
	else{
		if(blk[nx]) solve2(nx, u, v, l);
		else if(blk[ny]) solve2(ny, v, u, l);
		else solve3(u, v, l);
	} 
}
bool cmp(int a, int b){ return dep[a] > dep[b]; }
int main(){
	Type = read();
	n = read();
	for(int i = 1; i <= n; i++) a[i][0] = read(), a[i][1] = read(), a[i][2] = read();
	for(int i = 1; i < n; i++){
		int x = read(), y = read();
		add(x, y); add(y, x);
	} dfs(1, 0); dfs2(1, 1);
	// Divide blocks 
	static int id[N], dis[N];
	for(int i = 1; i <= n; i++) id[i] = i;
	sort(id + 1, id + n + 1, cmp); 
	for(int i = 1; i <= n; i++){
		for(int e = first[id[i]]; e; e = nxt[e]){
			int t = to[e]; if(t == fa[id[i]]) continue;
			dis[id[i]] = max(dis[id[i]], dis[t] + 1);
		} 
		if(id[i] == 1 || dis[id[i]] >= SIZ){
			dis[id[i]] = 0; blk[id[i]] = ++ct; key.push_back(id[i]);
		}
	}
	for(int i = 0; i < key.size(); i++){
		num = 0; memset(bin, 0, sizeof(bin));
		prework(blk[key[i]], key[i], 0);
	}
	memset(bin, 0, sizeof(bin));
	workupper(1, 0, 0);
	m = read();
	int Case = 0;
	while(m--){
		int u = read(), v = read();
		if(Type) u ^= xorsum, v ^= xorsum;
		query(u, v); 
	} return 0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

FSYo

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值