染色
P2486 [SDOI2011]染色
[SDOI2011]染色
题目描述
给定一棵 \(n\) 个节点的无根树,共有 \(m\) 个操作,操作分为两种:
- 将节点 \(a\) 到节点 \(b\) 的路径上的所有点(包括 \(a\) 和 \(b\))都染成颜色 \(c\)。
- 询问节点 \(a\) 到节点 \(b\) 的路径上的颜色段数量。
颜色段的定义是极长的连续相同颜色被认为是一段。例如 112221 由三段组成:11、222、1。
输入格式
输入的第一行是用空格隔开的两个整数,分别代表树的节点个数 \(n\) 和操作个数 \(m\)。
第二行有 \(n\) 个用空格隔开的整数,第 \(i\) 个整数 \(w_i\) 代表结点 \(i\) 的初始颜色。
第 \(3\) 到第 \((n + 1)\) 行,每行两个用空格隔开的整数 \(u, v\),代表树上存在一条连结节点 \(u\) 和节点 \(v\) 的边。
第 \((n + 2)\) 到第 \((n + m + 1)\) 行,每行描述一个操作,其格式为:
每行首先有一个字符 \(op\),代表本次操作的类型。
- 若 \(op\) 为
C,则代表本次操作是一次染色操作,在一个空格后有三个用空格隔开的整数 \(a, b, c\),代表将 \(a\) 到 \(b\) 的路径上所有点都染成颜色 \(c\)。 - 若 \(op\) 为
Q,则代表本次操作是一次查询操作,在一个空格后有两个用空格隔开的整数 \(a, b\),表示查询 \(a\) 到 \(b\) 路径上的颜色段数量。
输出格式
对于每次查询操作,输出一行一个整数代表答案。
样例 #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
提示
数据规模与约定
对于 \(100\%\) 的数据,\(1 \leq n, m \leq 10^5\),\(1 \leq w_i, c \leq 10^9\),\(1 \leq a, b, u, v \leq n\),\(op\) 一定为 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
*/
浙公网安备 33010602011771号