• 博客园logo
  • 会员
  • 众包
  • 新闻
  • 博问
  • 闪存
  • 赞助商
  • HarmonyOS
  • Chat2DB
    • 搜索
      所有博客
    • 搜索
      当前博客
  • 写随笔 我的博客 短消息 简洁模式
    用户头像
    我的博客 我的园子 账号设置 会员中心 简洁模式 ... 退出登录
    注册 登录

llwwll

  • 博客园
  • 联系
  • 订阅
  • 管理

公告

View Post

染色

P2486 [SDOI2011]染色

[SDOI2011]染色

题目描述

给定一棵 \(n\) 个节点的无根树,共有 \(m\) 个操作,操作分为两种:

  1. 将节点 \(a\) 到节点 \(b\) 的路径上的所有点(包括 \(a\) 和 \(b\))都染成颜色 \(c\)。
  2. 询问节点 \(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
*/

posted on 2022-11-06 18:14  llwwll  阅读(119)  评论(0)    收藏  举报

刷新页面返回顶部
 
博客园  ©  2004-2025
浙公网安备 33010602011771号 浙ICP备2021040463号-3