题目描述
给定一棵有n个节点的无根树和m个操作,操作有2类:
- 将节点a到节点b路径上所有点都染成颜色c;
- 询问节点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;
}