传送门:https://www.luogu.com.cn/problem/P7735
题目大意:给你一棵树,最开始树上的边都是轻边,支持操作:
1
x
y
1\ x\ y
1 x y:把
x
∼
y
x \thicksim y
x∼y路径上的边都变成重边,并把与这条路径相邻的边都变成轻边;
2
x
y
2\ x\ y
2 x y:查询
x
∼
y
x \thicksim y
x∼y路径上有多少条重边。
我寻思这不是经典题吗?NOI出原题???
这个操作让我们容易想到LCT的
a
c
c
e
s
s
access
access操作。如此我们可以知道:轻重边切换的总次数是
O
(
n
l
o
g
n
)
O(n log n)
O(nlogn)的,我们只需要想办法找到所有轻重边切换的位置然后模拟即可。
等等,说好的LCT不在NOI考纲内呢?
当然你可以不用LCT来写这个题,比如据我所知场上大多数人写的是树剖?但是我不管,作为一个LCT狂热爱好者,我偏要写LCT!
然后你盯着这个
1
1
1操作,心里想“这不就是LCT经典的
s
p
l
i
t
(
x
,
y
)
split(x,y)
split(x,y)操作吗我刚学LCT的时候就会了”,然后开始写的时候发现不对劲:正常的
s
p
l
i
t
(
x
,
y
)
split(x,y)
split(x,y)需要先
m
a
k
e
r
o
o
t
(
x
)
makeroot(x)
makeroot(x)再
a
c
c
e
s
s
(
y
)
access(y)
access(y),然后你就会在
m
a
k
e
r
o
o
t
(
x
)
makeroot(x)
makeroot(x)的时候顺手把
x
x
x到现在的根的路径全都变成重边,而题目里可没让咱这么干!
那怎么办?换根是别想了(因为
m
a
k
e
r
o
o
t
makeroot
makeroot操作不能做),那LCT有没有不换根写法呢?有!
我们只需要先找到
x
x
x与
y
y
y的LCA,不妨记作
w
w
w,然后魔改我们的
a
c
c
e
s
s
access
access函数:
a
c
c
e
s
s
(
x
,
w
)
access(x,w)
access(x,w)表示拎出一条
x
∼
w
x\thicksim w
x∼w的链塞进一个
s
p
l
a
y
splay
splay里,并且把链上原本的其他重边切断。
然后只需要
a
c
c
e
s
s
(
x
,
w
)
access(x,w)
access(x,w)再
a
c
c
e
s
s
(
y
,
w
)
access(y,w)
access(y,w)就行了,不过现在有一个小问题:
w
w
w处应该向儿子连出去两条重边,但是LCT显然不支持这么做。
解决办法很简单:两条重边都给他变成轻边!
也就是说,我们维护的LCT的轻重边是严格按照题目中的来的,除了LCA处如果有两条重边的话我们都给变成轻边。当然我们要在LCA处特别记录一下这两条轻边。
这样每次
a
c
c
e
s
s
access
access的时候,只需要在轻重边切换的时候特判一下就行了。
注意LCA处不要留一条重边一条轻边,不然下次
a
c
c
e
s
s
access
access的时候会因为找不到这条轻边而GG。
现在还剩最后一个问题:怎么查询?别忘了我们的LCT要维护真实的轻重边结构,所以我们显然不能在查询时
s
p
l
i
t
(
x
,
y
)
split(x,y)
split(x,y)出来。
不过这也好解决,由于我们没有换根,我们只需要想办法维护出每个点到根的路径上有多少条重边就行了。
在每次轻重边切换的时候,会对一个子树的答案产生影响,这就是DFS序上的区间修改,然后查询是单点查询。
额外开个树状数组维护就行。
强调一点:这道题LCT的作用只是用来找到需要修改的边,LCT本身是不维护信息的!
复杂度是
O
(
n
l
o
g
2
n
)
O(n log^2 n)
O(nlog2n),其中LCT的部分是
O
(
n
l
o
g
n
)
O(n log n)
O(nlogn)的,复杂度瓶颈主要在于树状数组的
O
(
n
l
o
g
n
)
O(n log n)
O(nlogn)次修改
O
(
n
)
O(n)
O(n)次查询。
相较于树剖的优势:首先LCT就是比树剖好写,这一点不接受反驳!(尤其是这个LCT只需要写到
a
c
c
e
s
s
access
access,不用换根不用
l
i
n
k
/
c
u
t
link/cut
link/cut)
其次唯一的复杂度瓶颈在于树状数组部分,众所周知树状数组常数比线段树(如果写树剖的话几乎不可避免)小得多;而虽然坊间传闻LCT常数巨大一个
l
o
g
log
log比树剖两个
l
o
g
log
log还慢,但是!我作为一个LCT爱好者必须澄清的是!在大部分不需要维护特别复杂的信息的题中,
1
0
5
10^5
105的数据范围就足以令LCT跑过树剖,而且数据范围越大LCT优势越明显!
代码如下:
#include<bits/stdc++.h>
char buf[100000],*buff = buf + 100000;
#define gc ((buff == buf + 100000 ? (fread(buf,1,100000,stdin),buff = buf) : 0),*(buff++))
char bfu[10000000],*bfuu = bfu;
#define pc(x) (*(bfuu++) = x)
using namespace std;
inline int read(){
int x = 0,c = gc;
while(c < '0' || c > '9') c = gc;
while(c >= '0' && c <= '9') x = x * 10 + c - '0',c = gc;
return x;
}
inline void print(int x){
if(x >= 10) print(x / 10);
pc(x % 10 + '0');
}
int T,n,m;
struct edge{
int to,nxt;
}e[200010];
int cnt,fir[100010];
inline void ins(int u,int v){
e[++cnt].to = v;e[cnt].nxt = fir[u];fir[u] = cnt;
e[++cnt].to = u;e[cnt].nxt = fir[v];fir[v] = cnt;
}
int t[100010],f[100010],l[100010],r[100010],s[100010];
bool g[100010];
int lgo[100010],st[17][100010],dpt[100010],wz[100010],ed[100010],tot;
int bj[100010][2];
inline void dfs(int q){
wz[q] = ++tot;s[q] = q;
for(int i = fir[q];i;i = e[i].nxt) if(e[i].to != f[q]){
f[e[i].to] = st[0][e[i].to] = q;
dpt[e[i].to] = dpt[q] + 1;
dfs(e[i].to);
}
ed[q] = tot;
}
inline void buildst(){
int i,j;
for(i = 1;i < 17;++i){
for(j = 1;j <= n;++j) st[i][j] = st[i - 1][st[i - 1][j]];
}
}
inline int lca(int x,int y){
if(dpt[x] < dpt[y]) swap(x,y);
int i = dpt[x] - dpt[y],j = 0;
while(i){
if(i & 1) x = st[j][x];
i >>= 1;++j;
}
if(x == y) return x;
for(i = lgo[dpt[x]];i >= 0;--i) if(st[i][x] != st[i][y]) x = st[i][x],y = st[i][y];
return st[0][x];
}
inline void xg(int x,int y){
for(int i = x;i <= n;i += i & -i) t[i] += y;
}
inline int cx(int x){
int as = 0;
for(int i = x;i;i -= i & -i) as += t[i];
return as;
}
inline void xx(int x,int y){
xg(wz[x],y);xg(ed[x] + 1,-y);
}
inline bool is(int q){
return l[f[q]] != q && r[f[q]] != q;
}
#define ud(x) (s[x] = (l[x] ? s[l[x]] : x))
inline void ro(int q){
int p = f[q];
if(l[f[p]] == p) l[f[p]] = q;
else if(r[f[p]] == p) r[f[p]] = q;
f[q] = f[p];f[p] = q;
if(l[p] == q){
l[p] = r[q];r[q] = p;
if(l[p]) f[l[p]] = p;
ud(p);
}
else{
r[p] = l[q];l[q] = p;
if(r[p]) f[r[p]] = p;
s[q] = s[p];
}
}
inline void sp(int q){
while(!is(q)){
int p = f[q];
if(!is(p)){
if((l[f[p]] == p) ^ (l[p] == q)) ro(q);
else ro(p);
}
ro(q);
}
}
inline int ac(int q,int w,int tp = 0){
if(g[w]){
int z = st[0][w];
xx(w,-1);g[w] = 0;
if(bj[z][0] == w) bj[z][0] = 0;
else bj[z][1] = 0;
sp(z);
r[z] = 0;
}
int p = 0;
while(q){
sp(q);
if(bj[q][0] && bj[q][0] != tp){
xx(bj[q][0],-1);
g[bj[q][0]] = 0;
bj[q][0] = 0;
}
if(bj[q][1] && bj[q][1] != tp){
xx(bj[q][1],-1);
g[bj[q][1]] = 0;
bj[q][1] = 0;
}
r[q] = p;
if(p){
if(!bj[q][0]) bj[q][0] = s[p];
else bj[q][1] = s[p];
xx(s[p],1);
g[s[p]] = 1;
}
if(s[q] == w) break;
p = q;
q = f[q];
}
sp(w);
return s[r[w]];
}
int main(){
int i,op,u,v,w;
for(i = 2;i <= 100000;++i) lgo[i] = lgo[i >> 1] + 1;
T = read();
while(T--){
memset(fir,0,sizeof(fir));
memset(t,0,sizeof(t));
memset(s,0,sizeof(s));
memset(f,0,sizeof(f));
memset(l,0,sizeof(l));
memset(r,0,sizeof(r));
memset(g,0,sizeof(g));
memset(st,0,sizeof(st));
memset(wz,0,sizeof(wz));
memset(bj,0,sizeof(bj));
tot = cnt = 0;
n = read();m = read();
for(i = 1;i < n;++i) ins(read(),read());
dfs(1);
buildst();
for(i = 1;i <= m;++i){
op = read();u = read();v = read();w = lca(u,v);
if(dpt[u] < dpt[v]) swap(u,v);
if(op == 1){
int tmp = ac(u,w);
if(v != w){
ac(v,w,tmp);
sp(w);r[w] = 0;
}
}
else{
int ans = cx(wz[u]) + cx(wz[v]) - 2 * cx(wz[w]);
print(ans);pc('\n');
}
}
}
fwrite(bfu,1,bfuu - bfu,stdout);
return 0;
}