1.13 上午-树链剖分

前言

我累个超绝 debug 难度啊!

勿让将来,辜负曾经

正文

知识点

树链剖分其实是一类算法的合称,如果只说树链剖分,我们认为是指重链剖分。抛却重链剖分,树链剖分还包含长链剖分(用于优化 DP),实链剖分(用于 LCT)

显然这节课是重链剖分的一次专题强化课捏!

树链剖分的核心思想是通过维护“重链”解决树上问题,理解重链建议结合图示哈!

云落盗张图——

重链剖分可用于维护 LCA,也可结合线段树维护链信息,子树信息等

安利一篇云落的早期博客,可能写得很抽象捏

传送门

一题一解

T1 【模板】重链剖分/树链剖分(P3384)

链接

模板题,不想多说,浅聊两句。

某一场模拟赛,云落顺利地 15 min 瞪出正解,是一个需要重链剖分维护的 DP。没有被 T1 降智的云落非常开心,然后那天就开心了这么一会……原因是,一个简单的树链剖分整场模拟赛云落都没有调出来,反复地重构确实让云落有点麻木了。现在想起来还是有点后怕,后来刷了一些树链剖分的题目,但是嘛——效果不佳。只能说树链剖分是一个熟能生巧的东西, 唉——((ε=(´ο`*)))。

这警示我们,模拟赛的时候不要死磕一道题(虽然那场模拟赛的其它题目云落一道也不会 qwq),并且功夫要下在平时。云落自认为给树链剖分这个算法分配的时间还算不少,板子也是敲了不下十遍咯,但是嘛,哑巴吃黄连哟……

不建议各位对着题解或者对着模板写树链剖分,建议充分理解后自己独立完成树链剖分模板题哈!

多唠叨一句,前几日云落的队友就链式前向星和 vector 实现邻接表存图哪个更优大干了一架。作为两种方法混用的云落瑟瑟发抖,如果屏幕前的你有食用树形 DP 题解中的代码的话,你会注意到云落两种方法都会用到。

事实上是这样的,云落倒不是真的随心所欲哈,还是有一些学问在里面。

链式前向星时空常数均略优于 vector 实现邻接表,但是这点小常数完全可以忽略不计。

云落想说的是,在面对一些复杂图论的情况下,具体地,存在边权,网络流等题目的时候,推荐使用链式前向星。毕竟一个结构体写的会很清楚,调用起来也很好看(而且云落不太了解网络流的 vector 写法,据说很复杂)。

这时候有人肯定会抬杠,vector 也可以套用结构体!你猜云落为什么要先说时空常数?当 vector 等其它 STL 套用非整型结构的时候,慢的离谱。

举两个实例哈,还是刚才那一场模拟赛,云落的一个很厉害的学长,写了一个 vector 套结构体,TLE 了 \(12\) 个点,和打暴力一个分数,改成链式前向星后抢到了时间最优解,就很离谱……另外一个栗子,云落和火腿肠在某一场模拟赛挂了 \(40pts\),原因是使用了平衡树的结构(其实是一个二分套二维数点问题),然后直接 T 飞了。云落用的 map<double,int> 离散化 + 树状数组维护,火腿肠手搓了一颗 FHQ_Treap,然后就双双起飞

但是如果说,一条边就只有孤零零的两个端点 \(u,v\),这时候云落就很喜欢用 vector 实现邻接表。不为别的,就为了好写。一部分原因是云落的码风比较冗长,所以能节省一些代码自然是节省一些比较好哇!

不多说了哈,有点跑题了,贴个模板代码跑路咯!

点击查看代码
#include<bits/stdc++.h>
#define endl '\n'
#define int long long
using namespace std;
const int maxn=1e5+10;
int n,m,rt,p,a[maxn];
vector<int> G[maxn];
int fa[maxn],son[maxn],dep[maxn],sz[maxn];
int dfn[maxn],tim,Top[maxn],rev[maxn];
struct Segment_tree{
	struct node{
		int l,r,sum,tag;
	}tr[maxn<<2];
	void pushup(int u){
		tr[u].sum=(tr[u<<1].sum+tr[u<<1|1].sum)%p;
		return;
	}
	void pushdown(int u){
		if(tr[u].tag==0){
			return;
		}
		int k=tr[u].tag;
		tr[u<<1].sum=(tr[u<<1].sum+k*(tr[u<<1].r-tr[u<<1].l+1)%p)%p;
		tr[u<<1|1].sum=(tr[u<<1|1].sum+k*(tr[u<<1|1].r-tr[u<<1|1].l+1)%p)%p;
		tr[u<<1].tag=(tr[u<<1].tag+k)%p;
		tr[u<<1|1].tag=(tr[u<<1|1].tag+k)%p;
		tr[u].tag=0;
		return;
	}
	void build(int u,int l,int r){
		tr[u].l=l;
		tr[u].r=r;
		if(l==r){
			tr[u].sum=rev[l]%p;
			return;
		}
		int mid=l+r>>1;
		build(u<<1,l,mid);
		build(u<<1|1,mid+1,r);
		pushup(u);
		return;
	}
	void modify(int u,int ql,int qr,int k){
		int l=tr[u].l,r=tr[u].r;
		if(ql<=l&&qr>=r){
			tr[u].sum=(tr[u].sum+k*(r-l+1)%p)%p;
			tr[u].tag=(tr[u].tag+k)%p;
			return;
		}
		pushdown(u);
		int mid=l+r>>1;
		if(ql<=mid){
			modify(u<<1,ql,qr,k);
		}
		if(qr>mid){
			modify(u<<1|1,ql,qr,k);
		}
		pushup(u);
		return;
	}
	int query(int u,int ql,int qr){
		int l=tr[u].l,r=tr[u].r;
		if(ql<=l&&qr>=r){
			return tr[u].sum%p;
		}
		pushdown(u);
		int mid=l+r>>1,res=0;
		if(ql<=mid){
			res=(res+query(u<<1,ql,qr))%p;
		}
		if(qr>mid){
			res=(res+query(u<<1|1,ql,qr))%p;
		}
		return res%p;
	}
}Tr;
inline void dfs1(int u,int fath){
	fa[u]=fath;
	dep[u]=dep[fath]+1;
	sz[u]=1;
	int mx=-1;
	for(int v:G[u]){
		if(v==fath){
			continue;
		}
		dfs1(v,u);
		sz[u]+=sz[v];
		if(sz[v]>mx){
			mx=sz[v];
			son[u]=v;
		}
	}
	return;
}
inline void dfs2(int u,int tp){
	dfn[u]=++tim;
	Top[u]=tp;
	rev[tim]=a[u];
	if(!son[u]){
		return;
	}
	dfs2(son[u],tp);
	for(int v:G[u]){
		if(v==fa[u]||v==son[u]){
			continue;
		}
		dfs2(v,v);
	}
	return;
}
inline void update_chain(int u,int v,int w){
	w%=p;
	while(Top[u]!=Top[v]){
		if(dep[Top[u]]<dep[Top[v]]){
			swap(u,v);
		}
		Tr.modify(1,dfn[Top[u]],dfn[u],w);
		u=fa[Top[u]];
	}
	if(dep[u]>dep[v]){
		swap(u,v);
	}
	Tr.modify(1,dfn[u],dfn[v],w);
	return;
}
inline int query_chain(int u,int v){
	int res=0;
	while(Top[u]!=Top[v]){
		if(dep[Top[u]]<dep[Top[v]]){
			swap(u,v);
		}
		res=(res+Tr.query(1,dfn[Top[u]],dfn[u]))%p;
		u=fa[Top[u]];
	}
	if(dep[u]>dep[v]){
		swap(u,v);
	}
	res=(res+Tr.query(1,dfn[u],dfn[v]))%p;
	return res;
}
inline void update_subtree(int u,int k){
	k%=p;
	Tr.modify(1,dfn[u],dfn[u]+sz[u]-1,k);
	return;
}
inline int query_subtree(int u){
	int res=Tr.query(1,dfn[u],dfn[u]+sz[u]-1)%p;
	return res;
}
signed main(){
	ios::sync_with_stdio(0);
	cin.tie(0);
	cout.tie(0);
	cin>>n>>m>>rt>>p;
	for(int i=1;i<=n;i++){
		cin>>a[i];
	}
	for(int i=1;i<=n-1;i++){
		int u,v;
		cin>>u>>v;
		G[u].push_back(v);
		G[v].push_back(u);
	}
	dfs1(rt,0);
	dfs2(rt,rt);
	Tr.build(1,1,n);
	for(int k=1;k<=m;k++){
		int opt;
		cin>>opt;
		if(opt==1){
			int x,y,z;
			cin>>x>>y>>z;
			update_chain(x,y,z);
		}else if(opt==2){
			int x,y;
			cin>>x>>y;
			int ans=query_chain(x,y)%p;
			cout<<ans<<endl;
		}else if(opt==3){
			int x,z;
			cin>>x>>z;
			update_subtree(x,z);
		}else if(opt==4){
			int x;
			cin>>x;
			int ans=query_subtree(x)%p;
			cout<<ans<<endl;
		}
	}
	return 0;
}

T2 软件包管理器(P2416)

链接

一句话题意:树上维护三个操作,路径修改,子树修改,全局和

上面那个板子稍微改改就好了哇!

一些细节:

  • 编号从 \(0\) 开始

  • tag 不建议初始化为 \(0\),存在赋值为 \(0\) 的操作

  • 如果你是直接把板子贴过来的,主函数的部分请留心

  • 虽然这道题目并不需要用到重编号,但是还是写一下形成习惯比较好

  • pushdown 最后给 tag 恢复初值的时候别写挂了

点击查看代码
#include<bits/stdc++.h>
#define endl '\n'
#define int long long
using namespace std;
const int maxn=2e5+10;
int n,q;
vector<int> G[maxn];
int fa[maxn],son[maxn],dep[maxn],sz[maxn];
int dfn[maxn],tim,Top[maxn],rev[maxn];
struct Segment_tree{
    struct node{
        int l,r,sum,tag;
    }tr[maxn<<2];
    void pushup(int u){
        tr[u].sum=tr[u<<1].sum+tr[u<<1|1].sum;
        return;
    }
    void pushdown(int u){
        if(tr[u].tag==-1){
            return;
        }
		int k=tr[u].tag;
        tr[u<<1].sum=k*(tr[u<<1].r-tr[u<<1].l+1);
        tr[u<<1|1].sum=k*(tr[u<<1|1].r-tr[u<<1|1].l+1);
        tr[u<<1].tag=k;
        tr[u<<1|1].tag=k;
        tr[u].tag=-1;
        return;
    }
    void build(int u,int l,int r){
        tr[u].l=l;
        tr[u].r=r;
        tr[u].tag=-1;
        if(l==r){
            return;
        }
        int mid=l+r>>1;
        build(u<<1,l,mid);
        build(u<<1|1,mid+1,r);
        return;
    }
    void modify(int u,int ql,int qr,int k){
        int l=tr[u].l,r=tr[u].r;
        if(ql<=l&&qr>=r){
            tr[u].sum=k*(r-l+1);
            tr[u].tag=k;
            return;
        }
        pushdown(u);
        int mid=l+r>>1;
        if(ql<=mid){
            modify(u<<1,ql,qr,k);
        }
        if(qr>mid){
            modify(u<<1|1,ql,qr,k);
        }
        pushup(u);
        return;
    }
    int query(int u,int ql,int qr){
        int l=tr[u].l,r=tr[u].r;
        if(ql<=l&&qr>=r){
            return tr[u].sum;
        }
        pushdown(u);
        int mid=l+r>>1,res=0;
        if(ql<=mid){
            res+=query(u<<1,ql,qr);
        }
        if(qr>mid){
            res+=query(u<<1|1,ql,qr);
        }
        return res;
    }
}Tr;
inline void dfs1(int u,int fath){
    fa[u]=fath;
    dep[u]=dep[fath]+1;
    sz[u]=1;
    int mx=-1;
	for(int v:G[u]){
		if(v==fath){
            continue;
        }
        dfs1(v,u);
        sz[u]+=sz[v];
        if(sz[v]>mx){
            mx=sz[v];
            son[u]=v;
        }
	}
    return;
}
inline void dfs2(int u,int tp){
    dfn[u]=++tim;
    Top[u]=tp;
    rev[tim]=u;
    if(son[u]==0){
        return;
    }
    dfs2(son[u],tp);
    for(int v:G[u]){
        if(v==fa[u]||v==son[u]){
            continue;
        }
        dfs2(v,v);
    }
    return;
}
inline void update(int u,int v,int k){
    while(Top[u]!=Top[v]){
        if(dep[Top[u]]<dep[Top[v]]){
            swap(u,v);
        }
        Tr.modify(1,dfn[Top[u]],dfn[u],k);
        u=fa[Top[u]];
    }
    if(dep[u]>dep[v]){
        swap(u,v);
    }
    Tr.modify(1,dfn[u],dfn[v],k);
    return;
}
signed main(){
    ios::sync_with_stdio(0);
    cin.tie(0);
    cout.tie(0);
    cin>>n;
    for(int i=2;i<=n;i++){
        int x;
        cin>>x;
		G[++x].push_back(i);
    }
    dfs1(1,0);
    dfs2(1,1);
    Tr.build(1,1,n);
    cin>>q;
    while(q--){
        string s;
        int x;
        cin>>s>>x;
        x++;
        int pre=Tr.tr[1].sum;
        if(s[0]=='i'){
            update(1,x,1);
            int cur=Tr.tr[1].sum;
            cout<<abs(cur-pre)<<endl;
        }else{
            Tr.modify(1,dfn[x],dfn[x]+sz[x]-1,0);
            int cur=Tr.tr[1].sum;
            cout<<abs(cur-pre)<<endl;
        }
    }
    return 0;
}

T3 旅行(P3313)

链接

山东省选是这样的……

首先,一眼树链剖分,维护路径修改,路径和与路径最大值

如果诸位对 P4556 雨天的尾巴 /【模板】线段树合并 还有印象的话,可能更容易想到这道题目的正解

倒不是说这道题要用什么线段树合并去维护,而是对于不同颜色建立不同的线段树的思想很类似

说人话就是,对于每一个宗教,我们都给它开一棵线段树,然后路径上的查询(QS 操作,QM 操作)就在相应的宗教对应的线段树上直接查询即可

两个修改更好维护咯,单点修改就记住先清空原有信息,再赋值更新信息

点击查看代码
#include<bits/stdc++.h>
#define endl '\n'
#define int long long
using namespace std;
const int maxn=1e5+10;
int n,q,w[maxn],c[maxn];
vector<int> G[maxn];
int fa[maxn],son[maxn],sz[maxn],dep[maxn];
int dfn[maxn],tim,Top[maxn],rev[maxn];
struct Segment_tree{
    struct node{
        int l,r,sum,mx;
    }tr[maxn<<5];
    int rt[maxn],cnt;
    void pushup(int u){
        tr[u].sum=tr[tr[u].l].sum+tr[tr[u].r].sum;
        tr[u].mx=max(tr[tr[u].l].mx,tr[tr[u].r].mx);
        return;
    }
    void modify(int &u,int l,int r,int pos,int k){
        if(u==0){
            u=++cnt;
        }
        if(l==r){
            tr[u].sum+=k;
            tr[u].mx=max(tr[u].mx,k);
            return;
        }
        int mid=l+r>>1;
        if(pos<=mid){
            modify(tr[u].l,l,mid,pos,k);
        }else{
            modify(tr[u].r,mid+1,r,pos,k);
        }
        pushup(u);
        return;
    }
    void del(int &u,int l,int r,int pos){
        if(l==r){
            tr[u].sum=0;
            tr[u].mx=0;
            return;
        }
        int mid=l+r>>1;
        if(pos<=mid){
            del(tr[u].l,l,mid,pos);
        }else{
            del(tr[u].r,mid+1,r,pos);
        }
        pushup(u);
        return;
    }
    int query1(int u,int l,int r,int ql,int qr){
        if(ql<=l&&qr>=r){
            return tr[u].sum;
        }
        int mid=l+r>>1,res=0;
        if(ql<=mid){
            res+=query1(tr[u].l,l,mid,ql,qr);
        }
        if(qr>mid){
            res+=query1(tr[u].r,mid+1,r,ql,qr);
        }
        return res;
    }
    int query2(int u,int l,int r,int ql,int qr){
        if(ql<=l&&qr>=r){
            return tr[u].mx;
        }
        int mid=l+r>>1,res=0;
        if(ql<=mid){
            res=max(res,query2(tr[u].l,l,mid,ql,qr));
        }
        if(qr>mid){
            res=max(res,query2(tr[u].r,mid+1,r,ql,qr));
        }
        return res;
    }
}Tr;
inline void dfs1(int u,int fath){
    fa[u]=fath;
    dep[u]=dep[fath]+1;
    sz[u]=1;
    int mx=-1;
    for(int v:G[u]){
        if(v==fath){
            continue;
        }
        dfs1(v,u);
        sz[u]+=sz[v];
        if(sz[v]>mx){
            mx=sz[v];
            son[u]=v;
        }
    }
    return;
}
inline void dfs2(int u,int tp){
    dfn[u]=++tim;
    Top[u]=tp;
    rev[tim]=u;
    if(son[u]==0){
        return;
    }
    dfs2(son[u],tp);
    for(int v:G[u]){
        if(v==fa[u]||v==son[u]){
            continue;
        }
        dfs2(v,v);
    }
    return;
}
inline int query_sum(int u,int v,int p){
    int res=0;
    while(Top[u]!=Top[v]){
        if(dep[Top[u]]<dep[Top[v]]){
            swap(u,v);
        }
        res+=Tr.query1(Tr.rt[p],1,n,dfn[Top[u]],dfn[u]);
        u=fa[Top[u]];
    }
    if(dep[u]>dep[v]){
        swap(u,v);
    }
    res+=Tr.query1(Tr.rt[p],1,n,dfn[u],dfn[v]);
    return res;
}
inline int query_max(int u,int v,int p){
    int res=0;
    while(Top[u]!=Top[v]){
        if(dep[Top[u]]<dep[Top[v]]){
            swap(u,v);
        }
        res=max(res,Tr.query2(Tr.rt[p],1,n,dfn[Top[u]],dfn[u]));
        u=fa[Top[u]];
    }
    if(dep[u]>dep[v]){
        swap(u,v);
    }
    res=max(res,Tr.query2(Tr.rt[p],1,n,dfn[u],dfn[v]));
    return res;
}
signed main(){
    ios::sync_with_stdio(0);
    cin.tie(0);
    cout.tie(0);
    cin>>n>>q;
    for(int i=1;i<=n;i++){
        cin>>w[i]>>c[i];
    }
    for(int i=1;i<=n-1;i++){
        int u,v;
        cin>>u>>v;
        G[u].push_back(v);
        G[v].push_back(u);
    }
    dfs1(1,0);
    dfs2(1,1);
    for(int i=1;i<=n;i++){
        Tr.modify(Tr.rt[c[i]],1,n,dfn[i],w[i]);
    }
    while(q--){
        string s;
        int x,y;
        cin>>s>>x>>y;
        if(s[1]=='C'){
            Tr.del(Tr.rt[c[x]],1,n,dfn[x]);
            Tr.modify(Tr.rt[y],1,n,dfn[x],w[x]);
            c[x]=y;
        }else if(s[1]=='W'){
            Tr.del(Tr.rt[c[x]],1,n,dfn[x]);
            Tr.modify(Tr.rt[c[x]],1,n,dfn[x],y);
            w[x]=y;
        }else if(s[1]=='S'){
            int ans=query_sum(x,y,c[x]);
            cout<<ans<<endl;
        }else if(s[1]=='M'){
            int ans=query_max(x,y,c[x]);
            cout<<ans<<endl;
        }
    }
    return 0;
}

T4 轻重边(P7735)

链接

2021 年的国赛题,还热乎!

首先,树上链的信息修改,一眼树链剖分

但似乎树上直接维护轻重边不好做(根本不能做好叭!),并且树链剖分也不适用于维护树边信息,而应维护结点信息。故此,一个经典 trick,把轻重边的判定转化为一条边两端点的判定

考虑出现重边的缘由,显然在一条路径上。那么如何用结点去描述一条路径捏?一个比较神奇的想法叫做染色。对于每一次路径修改,我们都考虑给这条路径上的每一个点染色(需要区别于所有其它已有颜色)。你会惊奇地发现,这个染色操作很有正确性保障。两端点颜色相同,则该树边是重边,否则是轻边

现在转化为树上维护颜色的问题,并且要对重边计数,树链剖分的板子还是风采依旧,转化为了线段树的一个区间信息维护问题

首先对于每一个区间,线段树要记录重边数量,也就是相邻点对颜色相同的数目。这个东西怎么用小区间合并捏?显然是与小区间的左右断电有关的,所以还要记录区间左右端点的信息

剩下的就比较好维护咯!

亿些细节:

  • 多测不清空,等着见祖宗

  • 路径修改的时候颜色种类一定要自增 \(1\)

  • pushuppushdown 的写法需要注意一下,区间染色和区间加的写法在 sum 以及 tag 上略有区别,并且不要忘记维护区间左右端点颜色

  • 线段树不建树也是要见祖宗的

  • 线段树向下查询需要一个特判,也是区间左右端点惹的祸

  • 考虑树链剖分向上跳跃的过程,我们不能只关注一条重链的信息,还需要关注重链的链顶与其父亲之间的颜色关系,所以也需要特判

剩下的奇奇怪怪的问题云落就不知道了捏!实在不会弄可以拿着云落的代码去对拍一下(别直接抄就行……)

点击查看代码
#include<bits/stdc++.h>
#define endl '\n'
#define int long long
using namespace std;
const int maxn=2e5+10;
int n,m,w[maxn],col;
vector<int> G[maxn];
int fa[maxn],son[maxn],dep[maxn],sz[maxn];
int dfn[maxn],tim,Top[maxn],rev[maxn];
struct Segment_tree{
    struct node{
        int l,r,col_l,col_r,sum,tag;
    }tr[maxn<<2];
    inline void pushup(int u){
        tr[u].sum=tr[u<<1].sum+tr[u<<1|1].sum+(tr[u<<1].col_r==tr[u<<1|1].col_l);
        tr[u].col_l=tr[u<<1].col_l;
        tr[u].col_r=tr[u<<1|1].col_r;
        return;
    }
    inline void pushdown(int u){
        if(tr[u].tag==0){
            return;
        }
        int l=tr[u].l,r=tr[u].r,k=tr[u].tag;
        int mid=l+r>>1;
        tr[u<<1].sum=mid-l;
        tr[u<<1|1].sum=r-mid-1;
        tr[u<<1].tag=k;
        tr[u<<1|1].tag=k;
        tr[u<<1].col_l=k;
        tr[u<<1].col_r=k;
        tr[u<<1|1].col_l=k;
        tr[u<<1|1].col_r=k;
        tr[u].tag=0;
        return;
    }
    inline void build(int u,int l,int r){
        tr[u].l=l;
        tr[u].r=r;
        tr[u].tag=0;
        if(l==r){
            tr[u].sum=0;
            tr[u].col_l=rev[l];
            tr[u].col_r=rev[l];
            return;
        }
        int mid=l+r>>1;
        build(u<<1,l,mid);
        build(u<<1|1,mid+1,r);
        pushup(u);
        return;
    }
    inline void modify(int u,int ql,int qr,int k){
        int l=tr[u].l,r=tr[u].r;
        if(ql<=l&&qr>=r){
            tr[u].col_l=k;
            tr[u].col_r=k;
            tr[u].tag=k;
            tr[u].sum=r-l;
            return;
        }
        pushdown(u);
        int mid=l+r>>1;
        if(ql<=mid){
            modify(u<<1,ql,qr,k);
        }
        if(qr>mid){
            modify(u<<1|1,ql,qr,k);
        }
        pushup(u);
        return;
    }
    inline int query(int u,int ql,int qr){
        int l=tr[u].l,r=tr[u].r;
        if(ql<=l&&qr>=r){
            return tr[u].sum;
        }
        pushdown(u);
        int mid=l+r>>1,res=0;
        if(ql<=mid){
            res+=query(u<<1,ql,qr);
        }
        if(qr>mid){
            res+=query(u<<1|1,ql,qr);
        }
        if(ql<=mid&&qr>mid&&tr[u<<1].col_r==tr[u<<1|1].col_l){
            res++;
        }
        return res;
    }
    inline int check(int u,int pos){
        int l=tr[u].l,r=tr[u].r;
        if(l==r){
            return tr[u].col_l;
        }
		pushdown(u); 
        int mid=l+r>>1;
        if(pos<=mid){
            return check(u<<1,pos);
        }else{
            return check(u<<1|1,pos);
        }
    }
}Tr;
inline void init(){
    for(int i=1;i<=n;i++){
        G[i].clear();
    }
    memset(w,0,sizeof(w));
    col=0;
    memset(fa,0,sizeof(fa));
    memset(son,0,sizeof(son));
    memset(dep,0,sizeof(dep));
    memset(sz,0,sizeof(sz));
    memset(dfn,0,sizeof(dfn));
    tim=0;
    memset(rev,0,sizeof(rev));
    memset(Top,0,sizeof(Top));
    return;
}
inline void dfs1(int u,int fath){
    fa[u]=fath;
    dep[u]=dep[fath]+1;
    sz[u]=1;
    int mx=-1;
    for(int v:G[u]){
        if(v==fath){
            continue;
        }
        dfs1(v,u);
        sz[u]+=sz[v];
        if(sz[v]>mx){
            mx=sz[v];
            son[u]=v;
        }
    }
    return;
}
inline void dfs2(int u,int tp){
    dfn[u]=++tim;
    Top[u]=tp;
    rev[tim]=w[u];
    if(son[u]){
        dfs2(son[u],tp);
    }
    for(int v:G[u]){
        if(v==fa[u]||v==son[u]){
            continue;
        }
        dfs2(v,v);
    }
    return;
}
inline void update(int u,int v){
    col++;
    while(Top[u]!=Top[v]){
        if(dep[Top[u]]<dep[Top[v]]){
            swap(u,v);
        }
        Tr.modify(1,dfn[Top[u]],dfn[u],col);
        u=fa[Top[u]];
    }
    if(dep[u]>dep[v]){
        swap(u,v);
    }
    Tr.modify(1,dfn[u],dfn[v],col);
    return;
}
inline int ask(int u,int v){
    int res=0;
    while(Top[u]!=Top[v]){
        if(dep[Top[u]]<dep[Top[v]]){
            swap(u,v);
        }
        res+=Tr.query(1,dfn[Top[u]],dfn[u]);
        if(Tr.check(1,dfn[fa[Top[u]]])==Tr.check(1,dfn[Top[u]])){
            res++;
        }
        u=fa[Top[u]];
    }
    if(dep[u]>dep[v]){
        swap(u,v);
    }
    res+=Tr.query(1,dfn[u],dfn[v]);
    return res;
}
inline void solve(){
    cin>>n>>m;
    for(int i=1;i<=n;i++){
        w[i]=++col;
    }
    for(int i=1;i<=n-1;i++){
        int u,v;
        cin>>u>>v;
        G[u].push_back(v);
        G[v].push_back(u);
    }
    dfs1(1,0);
    dfs2(1,1);
    Tr.build(1,1,n);
    while(m--){
        int opt,x,y;
        cin>>opt>>x>>y;
        if(opt==1){
            update(x,y);
        }else{
            int ans=ask(x,y);
            cout<<ans<<endl;
        }
    }
    return;
}
signed main(){
    ios::sync_with_stdio(0);
    cin.tie(0);
    cout.tie(0);
    int T;
    cin>>T;
    while(T--){
        init();
        solve();
    }
    return 0;
}

后记

工作量突然变少了,还有点不适应

完结撒花!

posted @ 2025-03-06 20:15  sunxuhetai  阅读(14)  评论(0)    收藏  举报