「SDOI2011」 染色 - 树链剖分

题目描述

给定一棵有n个节点的无根树和m个操作,操作有2类:

  1. 将节点a到节点b路径上所有点都染成颜色c;
  2. 询问节点a到节点b路径上的颜色段数量(连续相同颜色被认为是同一段),如“112221”由3段组成:“11”、“222”和“1”。

请你写一个程序依次完成这m个操作。

输入格式

第一行包含2个整数n和m,分别表示节点数和操作数;

第二行包含n个正整数表示n个节点的初始颜色

下面 行每行包含两个整数x和y,表示x和y之间有一条无向边。

下面 行每行描述一个操作:

C a b c表示这是一个染色操作,把节点a到节点b路径上所有点(包括a和b)都染成颜色c;

Q a b表示这是一个询问操作,询问节点a到节点b(包括a和b)路径上的颜色段数量。

输出格式

对于每个询问操作,输出一行答案。

数据范围

数据范围

分析

树上链的操作且为静态的(这里指树的形态为静态的),一般用树剖来做。首先两次Dfs将树剖成链,再用线段树维护。维护过程中记录三个值, l c , r c , s u m lc,rc,sum lc,rc,sum,分别为区间左边的颜色,区间右边的颜色,区间内颜色段的总数, P u s h u p Pushup Pushup时该区间左边颜色就等于左儿子的左边,右边等于右儿子的右边,总数等于左右总数之和,但若左儿子的右边等于右儿子的左边,就会重复计数,所以要减1。

在跑树中链时,用两个变量记录上一次剖得的链的左边颜色,用两个是因为有x和y,两边同时往上跳的点。之后累加完答案后,若当前区间的右边颜色等于上次剖得的链的左边颜色,则重复计数,要减1。到最后在同一条重链时,同理判断。修改时就用懒标记,需要用到时在 P u s h d o w n Pushdown Pushdown

要注意以下,在两个点往上跳交换两个点,使top深度最大的点更新时,要将两个变量也交换。

当然,此题可用LCT过。一般来说,凡是树剖能过A的题,LCT都可以A。

代码

#include <iostream>
#include <cstdio>
using namespace std;
const int N=100002;
struct Edge {
	int to,nxt;
}e[N<<1];
int h[N],cnt,temp[N];
int n,m,v[N],tag[N<<2],pre[N];
int sum[N<<2],lc[N<<2],rc[N<<2];
int size[N],son[N],prt[N];
int seg[N],top[N],dep[N];
void Add(int x,int y) {
	e[++cnt]=(Edge){y,h[x]};
	h[x]=cnt;
}
void Dfs1(int x,int fa) {
	prt[x]=fa;
	dep[x]=dep[fa]+1;
	size[x]=1;
	for (int i=h[x];i;i=e[i].nxt) {
		int y=e[i].to;
		if (y==fa) continue;
		Dfs1(y,x);
		size[x]+=size[y];
		if (size[son[x]]<size[y]) son[x]=y;
	}
}
void Dfs2(int x,int tp) {//以上两次Dfs将树剖成链 
	seg[x]=++seg[0];
	top[x]=tp;
	pre[seg[0]]=x;
	if (son[x]) Dfs2(son[x],tp);
	for (int i=h[x];i;i=e[i].nxt) {
		int y=e[i].to;
		if (top[y]) continue;
		Dfs2(y,y);
	}
}
void Pushup(int p) {//上传更新 
	sum[p]=sum[p<<1]+sum[p<<1|1];
	lc[p]=lc[p<<1];
	rc[p]=rc[p<<1|1];
	if (rc[p<<1]==lc[p<<1|1]) sum[p]--;
}
void Pushdown(int p) {//下传标记 
	if (!tag[p]) return;
	sum[p<<1]=sum[p<<1|1]=1;
	tag[p<<1]=tag[p<<1|1]=tag[p];
	lc[p<<1]=rc[p<<1]=tag[p];
	lc[p<<1|1]=rc[p<<1|1]=tag[p];
	tag[p]=0;
}
void Build(int p,int l,int r) {//建树 
	if (l==r) {
		sum[p]=1;
		lc[p]=rc[p]=v[pre[l]];
		return;
	}
	int mid=(l+r)>>1;
	Build(p<<1,l,mid);
	Build(p<<1|1,mid+1,r);
	Pushup(p);
}
int Query(int p,int l,int r,int L,int R,int &ll,int &rr) {
//p,l,r,L,R为正常线段树所传参数,ll为该区间内最左边的颜色,rr为最右边的颜色 
	if (L<=l&&r<=R) {
		ll=lc[p];
		rr=rc[p];
		return sum[p];
	}
	Pushdown(p);
	int mid=(l+r)>>1;
	if (R<=mid) return Query(p<<1,l,mid,L,R,ll,rr);//全左 
	else if (L>mid) return Query(p<<1|1,mid+1,r,L,R,ll,rr);//全右 
	else {//交错 
		int llp,lrp,rlp,rrp;//左左,左右,右左,右右
		int sum=Query(p<<1,l,mid,L,R,llp,lrp)+Query(p<<1|1,mid+1,r,L,R,rlp,rrp);
		if (lrp==rlp) sum--;
		ll=llp;
		rr=rrp;
		return sum;
	}
}
void Change(int p,int l,int r,int L,int R,int x) {
	if (L<=l&&r<=R) {
		sum[p]=1;
		lc[p]=rc[p]=x;
		tag[p]=x;
		return;
	}
	Pushdown(p);
	int mid=(l+r)>>1;
	if (L<=mid) Change(p<<1,l,mid,L,R,x);
	if (R>mid) Change(p<<1|1,mid+1,r,L,R,x);
	Pushup(p);
}
int Ask(int x,int y) {
	int xx=0,yy=0,ans=0,ll,rr,t;
	while (top[x]!=top[y]) {
		if (dep[top[x]]<dep[top[y]]) {
			swap(x,y);
			swap(xx,yy);
		}
		t=Query(1,1,seg[0],seg[top[x]],seg[x],ll,rr);
		ans+=t;
		if (rr==xx) ans--;//重复,减1 
		xx=ll;
		x=prt[top[x]];
	}
	if (dep[x]>dep[y]) {
		swap(x,y);
		swap(xx,yy);
	}
	t=Query(1,1,seg[0],seg[x],seg[y],ll,rr);
	ans+=t;
	if (xx==ll) ans--;//重复 
	if (yy==rr) ans--;
	return ans;
}
void Updata(int x,int y,int c) {//正常操作 
	while (top[x]!=top[y]) {
		if (dep[top[x]]<dep[top[y]]) swap(x,y);
		Change(1,1,seg[0],seg[top[x]],seg[x],c);
		x=prt[top[x]];
	}
	if (dep[x]>dep[y]) swap(x,y);
	Change(1,1,seg[0],seg[x],seg[y],c);
}
int main() {
	scanf("%d%d",&n,&m);
	for (int i=1;i<=n;i++)
		scanf("%d",&v[i]);
	for (int i=1;i<n;i++) {
		int u,v;
		scanf("%d%d",&u,&v);
		Add(u,v);
		Add(v,u);
	}
	Dfs1(1,1);
	Dfs2(1,1);
	Build(1,1,seg[0]);
	for (int i=1;i<=m;i++) {
		char op[10];
		int x,y,z;
		scanf("%s%d%d",op,&x,&y);
		if (op[0]=='C') {
			scanf("%d",&z);
			Updata(x,y,z);
		} else {
			printf("%d\n",Ask(x,y));
		}
	}
	return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值