洛谷P2486 [SDOI2011]染色(树链剖分初入门)

在这里插入图片描述
首先,根据本人以往解决经验这类疑似树上区间操作,都可能是树链剖分,我们先考虑数组的实现情况
给你一个数组 a i ai ai,有2个操作
操作 1 : [ l , r , c o l o r ] : [ l , r ] 变成 c o l o r 色 操作1:[l,r,color]:[l,r]变成color色 操作1:[l,r,color]:[l,r]变成color
操作 2 : 查询 [ l , r ] 颜色段的数目 操作2:查询[l,r]颜色段的数目 操作2:查询[l,r]颜色段的数目
这是线段树染色的一个东西,解决方法如下:
线段树维护3个值 区间颜色数量 n u m , 左端点颜色 c l , 右端点颜色 c r 区间颜色数量num,左端点颜色cl,右端点颜色cr 区间颜色数量num,左端点颜色cl,右端点颜色cr
考虑区间之间的合并:
如果左区间 L L L c r cr cr与右区间 R R R c l cl cl一样,说明有一段染色横跨了两个区间.那么总的染色数目会减少1. n u m [ f a ] = n u m [ L ] + n u m [ R ] − 1 num[fa]=num[L]+num[R]-1 num[fa]=num[L]+num[R]1
否则,两个区间之间没有交叉,颜色数目都是独立的. n u m [ f a ] = n u m [ L ] + n u m [ R ] num[fa]=num[L]+num[R] num[fa]=num[L]+num[R]
解决完核心的区间合并问题,我们就可以尝试书写线段树部分并套用树链剖分即可.
很快我们发现仍然有问题,因为线段树部分只能在重链部分是有效的,我们需要人为去合并重链之间的信息.
当让 d e p t h [ x ] depth[x] depth[x]较大的点往上跳的时候,记录前一次跳的时候较浅的点的颜色(就是线段树查询里边的左端点的颜色,因为深度小的结点在线段树映射的id也小),如果查询到的链条的右端点与上一次的左端点颜色相同,那么它们本来是同一段颜色,应当减一.
在这里插入图片描述
基于这个想法:我们用 c l cl cl记录上一次 x x x跳完后左端点区间的颜色,也就是 t o p [ x ] top[x] top[x]的颜色,用 c r cr cr记录 c l cl cl上一次更替的值.
首先, c l cl cl是为了帮助我们解决上边的问题的,也就是 x x x往上跳到新的重链的时候, 之前的 t o p [ x ] 之前的top[x] 之前的top[x]与新链最下边的颜色是否相同,如果相同那么则要减少1,因为他们属于同一个颜色块.
有代码 i f ( c l = = t . c r ) a n s − − if(cl==t.cr) ans-- if(cl==t.cr)ans, t . c r t.cr t.cr是本次跳到新链后最下边的那个点.
然而,因为这个 x x x跳到 L c a ( x , y ) Lca(x,y) Lca(x,y)的过程是两个点反复横跳的过程,所以我们还需要一个 c r cr cr来记录另外一边(也就是y那边)对应的那个 c l cl cl.
当我们执行了 i f ( d e p t h [ t o p [ x ] ] < d e p t h [ t o p [ y ] ] ) s w a p ( x , y ) if(depth[top[x]]<depth[top[y]]) swap(x,y) if(depth[top[x]]<depth[top[y]])swap(x,y),点 x , y x,y x,y发生了变化,那么它们的 c l , c r cl,cr cl,cr也要交换一边,
要保证当前跳的这个点的 x x x,它底下那个点是 c l cl cl.用图像来表示,就会看到以下图片.
在这里插入图片描述
特别的,如果某个点到了 l c a ( x , y ) lca(x,y) lca(x,y),也就是处以同一块重链的时候,我们会完成最后的跳跃.但请特别注意,此时 x , y x,y x,y这两个点已经被之前的两个段计算过了,所以如果 x , y x,y x,y这两个点如果和 c l , c r cl,cr cl,cr有重复的颜色,那么答案要相应减少
也就是
这里的 t r . c l tr.cl tr.cl就是 x x x, t r . c r tr.cr tr.cr就是 y y y.在最后我们保证了 d e p t h [ x ] depth[x] depth[x]是较大的.
i f ( c l = = t r . c l ) a n s − − if(cl==tr.cl)ans-- if(cl==tr.cl)ans i f ( c r = = t r . c r ) a n s − − if(cr==tr.cr)ans-- if(cr==tr.cr)ans
148行痛苦ac代码:
第一次尝试写详细的树链剖分题解,还不是很熟练

/*
You held me down but I broke free,
I found the love inside of me.
Now I don't need a hero to survive
Cause I already saved my life.
*/
#include<bits/stdc++.h>
using namespace std;
const int maxn = 1e6+5;
const int INF = 1e9+7;
typedef long long ll;
typedef pair<int,int> pii;
#define all(a) (a).begin(), (a).end()
#define pb(a) push_back(a)
int a[maxn];int tag[maxn*4+5];
//线段树部分
#define L (idx<<1)
#define R (idx<<1|1)
#define MID (start+end>>1)
struct Node{
	int num,cl,cr;
}tree[maxn*4+5];
void push_up(int idx){
	if(tree[L].cr==tree[R].cl) tree[idx].num = tree[L].num + tree[R].num -1;
	else tree[idx].num = tree[L].num + tree[R].num;
	//maintain cl,cr;
	tree[idx].cl = tree[L].cl;tree[idx].cr = tree[R].cr;
}
void build(int idx,int start,int end){
	if(start==end) {
		tree[idx].cl = tree[idx].cr = a[start];
        tree[idx].num = 1;
		return ;
	}
	build(L,start,MID);build(R,MID+1,end);
	push_up(idx);
}
void push_down(int idx,int start,int end){
	if(tag[idx]==0) return ;
	//pushdown的子节点全部被染色成tag[idx],颜色段也改成1
	tree[L].cl = tree[L].cr = tree[R].cl = tree[R].cr = tag[idx];
	tree[L].num = tree[R].num =1;
	tag[L] = tag[R] = tag[idx];
	tag[idx]=0;
}
void update(int idx,int start,int end,int l,int r,int k){
	if(start>=l&&end<=r){
		tree[idx].cl = tree[idx].cr = k;tree[idx].num =1;
		tag[idx] = k;
		return ;
	}
	push_down(idx,start,end);
	if(l<=MID) update(L,start,MID,l,r,k);
	if(r>MID) update(R,MID+1,end,l,r,k);
	push_up(idx);
}
Node query(int idx,int start,int end,int l,int r){
	if(start>=l&&end<=r) return tree[idx];
	push_down(idx,start,end);
	if(l<=MID&&r>MID){
		Node t1 = query(L,start,MID,l,r);Node t2 = query(R,MID+1,end,l,r);
		if(t1.cr==t2.cl) return (Node){t1.num+t2.num-1,t1.cl,t2.cr};
		return (Node){t1.num+t2.num,t1.cl,t2.cr};
	}
	else if(l<=MID) return query(L,start,MID,l,r);
	return query(R,MID+1,end,l,r);
}
//接下来是树链剖分的内容
int son[maxn],fa[maxn],depth[maxn],top[maxn],siz[maxn];int dfs_clock=0;//dfs序
vector<int> G[maxn];int n;//图的大小,树一共有多少节点
//u的重儿子,父亲,深度,顶端重儿子编号,u为子树的节点大小.
int val[maxn],id[maxn];//剖分后,每个点得到一个新的连续编号,在同一链上,id为连续的,故可线段树处理
void dfs1(int u,int f){
	depth[u]=depth[f]+1;fa[u]=f;siz[u]=1;
	for(auto v : G[u]){
		if(v==f) continue;
		dfs1(v,u);siz[u]+=siz[v];
		if(son[u]==0||siz[son[u]]<siz[v]) son[u]=v;
	}
}
void dfs2(int u,int topf){
	id[u]=++dfs_clock;a[id[u]]=val[u];//把原点权赋值到线段树上
	top[u]=topf;
	if(son[u]) dfs2(son[u],topf);
	for(auto v : G[u]){
		if(v==fa[u]||v==son[u]) continue;
		dfs2(v,v);
	}
}
//树链剖分与线段树连接部分
//1.查询树上x,y路径上点权和 在一条链上,top[x]实际上是该链编号的起点
//因为dfs序一定先访问到了top[x],再访问到x,查询范围就是[id[top[x],id[x]]
int tr_query(int x,int y){
	int cl = 0,cr = 0;
	int ans = 0;
	while(top[x]!=top[y]){
		//当x,y不再同一个链上,需要先类似倍增思想跳到同一个链上
		//默认x为较深的点
		if(depth[top[x]]<depth[top[y]]) swap(x,y),swap(cl,cr);
		Node t = query(1,1,n,id[top[x]],id[x]);
		ans += t.num;
		if(t.cr==cl) ans--;
		cl = t.cl;
		x = fa[top[x]];//跳到一条新的链上
	}
	if(depth[x]>depth[y]) swap(x,y),swap(cl,cr);
	//默认x的depth较小,则id[x]即为较小的那个,此时已经在同一链上
	Node t = query(1,1,n,id[x],id[y]);ans+=t.num;
	if(cl==t.cl) ans--;
	if(cr==t.cr) ans--;
	return ans;
}
//2.对于树上两个节点x,y, 其路径上全部加上权值val
//同1操作即可
void tr_update(int x,int y,int k){
	while(top[x]!=top[y]){
		if(depth[top[x]]<depth[top[y]]) swap(x,y);
		update(1,1,n,id[top[x]],id[x],k);
		x = fa[top[x]];
	}
	if(depth[x]>depth[y]) swap(x,y);
	update(1,1,n,id[x],id[y],k);
}
int main(){
    ios::sync_with_stdio(false);
	cin.tie(0);
	cout.tie(0);
	int m;cin>>n>>m;
	for(int i=1;i<=n;i++) cin>>val[i];
	for(int i=1;i<=n-1;i++){
		int u,v;cin>>u>>v;
		G[u].pb(v);G[v].pb(u);
	}
	dfs1(1,0);
	dfs2(1,1);
	build(1,1,n);
	while(m--){
		char op;cin>>op;
		if(op=='C'){
			int a,b,c;cin>>a>>b>>c;
			tr_update(a,b,c);
		}
		else{
			int a,b;cin>>a>>b;
			cout<<tr_query(a,b)<<"\n";
		}
	}
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

minato_yukina

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

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

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

打赏作者

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

抵扣说明:

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

余额充值