题意:给一棵树,每个节点有 3 个权值,问路径众数,次数相同以最小的为众数
n
≤
8
e
4
,
q
≤
1
e
5
,
512
M
B
,
10
s
n\le 8e4,q\le1e5,512MB,10s
n≤8e4,q≤1e5,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(q∗log(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 一遍即可
查询的细节
对两个点能不能找到关键点讨论
- 两个都没有,暴力跳
- 一个有,在有的那边继续往上跳,跳到离
l
c
a
lca
lca 最近的,
暴力跳令一边和离 l c a lca lca最近的关键点到 l c a lca lca的那一部分,容易发现不管怎么跳都是 n \sqrt n n 的 - 两个都有,直接插那两个的答案即可
建议使用只带一个
n
\sqrt n
n, 而不是
n
∗
l
o
g
(
n
)
\sqrt {n*log(n)}
n∗log(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;
}