染色
P2486 [SDOI2011]染色
[SDOI2011]染色
题目描述
给定一棵
- 将节点
到节点 的路径上的所有点(包括 和 )都染成颜色 。 - 询问节点
到节点 的路径上的颜色段数量。
颜色段的定义是极长的连续相同颜色被认为是一段。例如 112221
由三段组成:11
、222
、1
。
输入格式
输入的第一行是用空格隔开的两个整数,分别代表树的节点个数
第二行有
第
第
每行首先有一个字符
- 若
为C
,则代表本次操作是一次染色操作,在一个空格后有三个用空格隔开的整数 ,代表将 到 的路径上所有点都染成颜色 。 - 若
为Q
,则代表本次操作是一次查询操作,在一个空格后有两个用空格隔开的整数 ,表示查询 到 路径上的颜色段数量。
输出格式
对于每次查询操作,输出一行一个整数代表答案。
样例 #1
样例输入 #1
6 5
2 2 1 2 1 1
1 2
1 3
2 4
2 5
2 6
Q 3 5
C 2 1 1
Q 3 5
C 5 1 2
Q 3 5
样例输出 #1
3
1
2
提示
数据规模与约定
对于 C
或 Q
,保证给出的图是一棵树。
除原数据外,还存在一组不计分的 hack 数据。
分析
树链剖分 线段树
对于一颗树,我们将其拆为数条链,查询两个节点之间的颜色数量及修改。显然,用线段树维护这几条链的信息(包括颜色数量,左端右端颜色),查询颜色数量时,如果右子树的左端点颜色与左子树的右端点颜色相同,根的颜色数量减一。
思路不难,但是代码实现由很多小细节—(本人因压行将,打成;调了一个下午......)
#include<bits/stdc++.h>
using namespace std;
const int maxn = 300010;
const int N = 1000010;
const int inf = 2147483647;
template<typename T>void read(T &x)
{
x=0; char c=getchar(); T neg=0;
while(!isdigit(c)) neg|=!(c^'-'),c=getchar();
while(isdigit(c)) x=(x<<3)+(x<<1)+(c^48),c=getchar();
if(neg) x=(~x)+1;
}
template<typename T>void wr(T x)
{
if(x<0) putchar('-'),x=-x;
if(x>9) wr(x/10);
putchar((x-x/10*10)^48);
return ;
}//快读快写
int a,b,c;
int col[maxn],n,m;
char C;
int tot,head[maxn],ver[N],nex[N],edge[N],dep[maxn],fa[maxn],siz[maxn],son[maxn];
void add(int u,int v) { ver[++tot]=v; nex[tot]=head[u]; head[u]=tot; }
void dfs1(int x,int pre)//第一次 dfs 记录重儿子,深度 ,父节点
{
dep[x]=dep[pre]+1; siz[x]=1; fa[x]=pre;
for(int i=head[x];i;i=nex[i])
{
int v=ver[i];
if(v==pre)continue;
dfs1(v,x);
siz[x]+=siz[v];
if(siz[v]>siz[son[x]]) son[x]=v;//更新重儿子
}
}
int top[maxn],dfn[maxn],rev[maxn],tim;
void dfs2(int x,int tp)
{
top[x]=tp; dfn[x]=++tim; rev[tim]=x;//记录这条链的顶端, 该点的 dfs 序, 记录时间戳(dfs序) 对应的点(用于线段树建树)
cout<<x<<" = "<<dfn[x]<<endl;
if(son[x]) dfs2(son[x],tp);//优先处理重儿子
for(int i=head[x];i;i=nex[i])
{
int v=ver[i];
if(v==fa[x]||v==son[x]) continue;
dfs2(v,v);//重新建立一条新链
}
}
struct node{
int l,r,cor,col,val,tag;
}tr[maxn<<2];
void push_up(int x)//向上更新
{
int L=tr[x<<1].cor,R=tr[x<<1|1].col;
tr[x].col=tr[x<<1].col;
tr[x].cor=tr[x<<1|1].cor;
tr[x].val=tr[x<<1].val+tr[x<<1|1].val;// 注意处理根节点颜色数量
if(L==R) tr[x].val--;
}
void push_down(int x)//下放操作
{
if(tr[x].tag)
{
tr[x<<1].tag=tr[x<<1|1].tag=tr[x].tag;
tr[x<<1].col=tr[x<<1|1].col=tr[x].tag;
tr[x<<1].cor=tr[x<<1|1].cor=tr[x].tag;
tr[x<<1].val=tr[x<<1|1].val=1;
tr[x].tag=0;
}
}
node merge(node a,node b)//合并两个区间颜色 a为右端点 b为左端点
{
node c;
int L=a.cor,R=b.col;
c.l=a.l; c.r=b.r;
c.col=a.col; c.cor=b.cor;
c.val=a.val+b.val;
if(L==R) c.val--; return c;//如果左子树右端点颜色与右子树左端点颜色相同吗 , 根节点颜色减一
}
void build_tim(int x,int l,int r)
{
tr[x].l=l;tr[x].r=r;tr[x].tag=0;
if(l==r)
{
tr[x].col=tr[x].cor=col[rev[l]];//根据时间戳建立线段树
tr[x].val=1; return ;
}
int mid=(l+r)>>1;
build_tim(x<<1,l,mid); build_tim(x<<1|1,mid+1,r);
push_up(x);
}
void upd_tim(int x,int L,int R,int c)
{
int l=tr[x].l,r=tr[x].r;
if(L<=l&&r<=R)
{
tr[x].col=tr[x].cor=tr[x].tag=c;//跟新覆盖区块
tr[x].val=1; return ;
}
push_down(x);
int mid=(l+r)>>1;
if(L<=mid) upd_tim(x<<1,L,R,c);//查询跟新所覆盖的区域
if(R>mid) upd_tim(x<<1|1,L,R,c);
push_up(x);
}
node ask_tim(int x,int L,int R)
{
node ll,rr;
int l=tr[x].l,r=tr[x].r;
if(L<=l&&r<=R)
{
return (node) {tr[x].l, tr[x].r, tr[x].col, tr[x].cor, tr[x].val };
}
push_down(x);
int mid=(l+r)>>1;
if(R<=mid) return ll=ask_tim(x<<1,L,R);//如果当前区间已经在查询区域左边,只需要查询右子树
if(L>mid) return rr=ask_tim(x<<1|1,L,R);//同理
return merge(ask_tim(x<<1,L,R),ask_tim(x<<1|1,L,R));//如果都有覆盖同时查询器贡献
}
void upd(int a,int b,int c)
{
while(top[a]!=top[b])//查询两链的顶点不同
{
if(dep[top[a]]<dep[top[b]]) swap(a,b);// 优先处理时间戳大的颜色链
upd_tim(1,dfn[top[a]],dfn[a],c);//跟新此颜色块
a=fa[top[a]];//该节点跳到下一条链
}
if(dep[a]<dep[b]) swap(a,b); //此时两点都在链的顶点处,且互为父子节点,将a跟新为时间戳大的节点
upd_tim(1,dfn[b],dfn[a],c);//跟新最后剩余节点
}
int ask(int a,int b)
{
node resa=(node){0,0,0,0,0},resb=(node){0,0,0,0,0};
while(top[a]!=top[b])
{
if(dep[top[a]]<dep[top[b]]) swap(a,b), swap(resa,resb);//同时交换当前查询的答案 (就是这个地方 ,打成 ;调了一个下午
resa=merge(ask_tim(1,dfn[top[a]],dfn[a]),resa);
a=fa[top[a]];
}
if(dep[a]<dep[b]) swap(a,b), swap(resa,resb); //同理
resa=merge(ask_tim(1,dfn[b],dfn[a]),resa);// a 作为 较大的时间戳,是右端点,注意合并的顺序
// cout<<b<<" = "<<dfn[b]<<"--"<<a<<" = "<<dfn[a]<<endl;
int val=resa.val+resb.val;
// cout<<resa.val<<" "<<resb.val<<" "<<resa.l<<"---"<<resa.r<<" "<<resb.l<<"---"<<resb.r<<endl;;
// cout<<resa.col<<"---"<<resa.cor<<" "<<resb.col<<"---"<<resb.cor<<endl;
if(resa.col==resb.col) --val;
return val;
}
signed main()
{
// freopen("dye.in","r",stdin);
// freopen("dye.out","w",stdout);
read(n); read(m);
for(int i=1;i<=n;i++) read(col[i]);
for(int i=1;i<n;i++) {int u,v; read(u); read(v); add(u,v); add(v,u); }
dfs1(1,1); dfs2(1,1); //
build_tim(1,1,n);//
while(m--)
{
cin>>C;
if(C=='C'){ read(a); read(b); read(c); upd(a,b,c); }
if(C=='Q'){ read(a); read(b); wr(ask(a,b)); putchar('\n'); }
}
return 0;
}
/*
6 5
2 2 1 2 1 1
1 2
1 3
2 4
2 5
2 6
Q 3 5
C 2 1 1
Q 3 5
C 5 1 2
Q 3 5
*/
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!