[luogu7735] [NOI2021] 轻重边 - 数据结构 - LCT - 树链剖分

传送门:https://www.luogu.com.cn/problem/P7735
题目大意:给你一棵树,最开始树上的边都是轻边,支持操作:
1   x   y 1\ x\ y 1 x y:把 x ∼ y x \thicksim y xy路径上的边都变成重边,并把与这条路径相邻的边都变成轻边;
2   x   y 2\ x\ y 2 x y:查询 x ∼ y x \thicksim y xy路径上有多少条重边。
我寻思这不是经典题吗?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 xw的链塞进一个 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;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值