BZOJ 2243: [SDOI2011]染色 树链剖分

Description

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

1、将节点a到节点b路径上所有点都染成颜色c

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

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

Input

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

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

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

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

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

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

Output

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

Sample Input

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

Sample Output

3

1

2

HINT

数N<=10^5,操作数M<=10^5,所有的颜色C为整数且在[0, 10^9]之间。

Source


此题的话树链剖分是很容易看出来的……

思路比较简单,我们线段树维护的时候记录一下:
区间最前端的颜色Lc,
区间最后端的颜色Rc,
以及这个区间内的颜色段数num。
合并的时候思路也很简单,num[Left]+num[Right]-Lc[Right]==Rc[Left]。
Left和Right是一个点的左右儿子。
这样子的话线段树维护就解决了。
同时更改颜色用lazy mark。

然而还有一个问题,就是在树链剖分的求解过程中,两个节点往上跳的合并问题。
因为一会儿你在左边跳u,一会儿又在右边跳v,这个时候合并就不能简单地看上面的方法。
我们记录一个数组t[0..1],然后在左边跳的时候信息记录到t[0],右边到t[1]。
接着,根据树链剖分的模板,每次跳一个top深度更深的节点,
那么一开始设一个变量(以下称为cc)。
一开始是0,然后每次swap的时候,cc=cc xor 1.
在最后u~v更新的过程中,反过来,不swap,cc=cc xor 1.
就是跳来跳去的一种思想……然后每次可以和t[0],t[1]合并了,并且记录到t[0],t[1]。
可能说不大清楚……具体见我代码吧。

对了……就是query的时候传回来的信息其实只有上面说过的3个,但是线段树有4个(还有个mark)
懒得弄了(唔
……于是多了个mark,也不管了。

最后谈谈感想……
很坑!洛谷上提交RE,然后数组从10^5多开了一倍,就AC了……
BZOJ上提交的时候数组改成了10^5+50000,也AC了,没什么心情去测10^5能否A。
这题主要还是合并的问题……其它没什么难点吧。

坑啊!代码长,而且我的代码跑起来还出奇地慢QAQ。。。


#include<bits/stdc++.h>
using namespace std;
int read(){
	int x=0,f=1;char ch=getchar();
	while (ch<'0' || ch>'9'){if (ch=='-') f=-1;ch=getchar();}
	while (ch>='0' && ch<='9'){x=x*10+ch-'0';ch=getchar();}
	return x*f;
}
const int 
	N=150005;
int n,m,cnt,Ecnt;
int col[N];
struct Edge{
	int next,to;
}E[N<<1]; int head[N];
struct Tree{
	int pre,son,deep,top,sz,tid;
}tree[N];
struct Segment{
	int mark,lc,rc,numc;
}tr[N<<2];
void add(int u,int v){
	E[++Ecnt].next=head[u];
	E[Ecnt].to=v;
	head[u]=Ecnt;
}
void up(int x){
	int l=x<<1,r=x<<1|1;
	tr[x].numc=tr[l].numc+tr[r].numc-(tr[l].rc==tr[r].lc);
	tr[x].lc=tr[l].lc,tr[x].rc=tr[r].rc;
}
void down(int x){
	if (tr[x].mark){
		int l=x<<1,r=x<<1|1;
		tr[l].mark=tr[r].mark=tr[x].mark;
		tr[l].numc=tr[r].numc=1;
		tr[l].lc=tr[l].rc=tr[r].lc=tr[r].rc=tr[x].mark;
		tr[x].mark=0;
	}
}
void update(int id,int l,int r,int gl,int gr,int color){
	down(id);
	if (l>=gl && r<=gr){
		tr[id].mark=color;
		tr[id].lc=tr[id].rc=color;
		tr[id].numc=1;
		return;
	}
	int mid=(l+r)>>1;
	if (gr<=mid) update(id<<1,l,mid,gl,gr,color);	else
	if (gl>mid) update(id<<1|1,mid+1,r,gl,gr,color);
		else{
			update(id<<1,l,mid,gl,mid,color);
			update(id<<1|1,mid+1,r,mid+1,gr,color);
		}
	up(id);
}
Segment query(int id,int l,int r,int gl,int gr){
	down(id);
	if (l>=gl && r<=gr) return {0,tr[id].lc,tr[id].rc,tr[id].numc};
	int mid=(l+r)>>1;
	if (gr<=mid) return query(id<<1,l,mid,gl,gr);	else
	if (gl>mid) return query(id<<1|1,mid+1,r,gl,gr);
		else{
			Segment t1=query(id<<1,l,mid,gl,mid),
				t2=query(id<<1|1,mid+1,r,mid+1,gr);
			return {0,t1.lc,t2.rc,t1.numc+t2.numc-(t1.rc==t2.lc)};
		}
}
void build(int x,int pre,int depth){
	tree[x].sz=1;
	tree[x].deep=depth;
	tree[x].pre=pre;
	int maxx=0;
	for (int i=head[x];i;i=E[i].next){
		int j=E[i].to;
		if (j==pre) continue;
		build(j,x,depth+1);
		tree[x].sz+=tree[j].sz;
		if (maxx<tree[j].sz){
			maxx=tree[j].sz;
			tree[x].son=j;
		}
	}
}
void getlink(int x,int ancestor){
	tree[x].top=ancestor;
	tree[x].tid=++cnt;
	if (tree[x].son) getlink(tree[x].son,ancestor);
	for (int i=head[x];i;i=E[i].next){
		int j=E[i].to;
		if (j==tree[x].pre || j==tree[x].son) continue;
		getlink(j,j);
	}
}
void change(int u,int v,int color){
	while (tree[u].top!=tree[v].top){
		if (tree[tree[u].top].deep<tree[tree[v].top].deep) swap(u,v);
		update(1,1,n,tree[tree[u].top].tid,tree[u].tid,color);
		u=tree[tree[u].top].pre;
	}
	if (tree[u].deep>tree[v].deep) swap(u,v);
	update(1,1,n,tree[u].tid,tree[v].tid,color);
}
int answer(int u,int v){
	bool cc;
	Segment t[2],tmp;
	t[0]=t[1]={0,0,0,0};   //左右跳跃的数组
	cc=0;
	while (tree[u].top!=tree[v].top){
		if (tree[tree[u].top].deep<tree[tree[v].top].deep) swap(u,v),cc^=1;
		tmp=query(1,1,n,tree[tree[u].top].tid,tree[u].tid);
		if (t[cc].rc) t[cc]={0,tmp.lc,t[cc].rc,tmp.numc+t[cc].numc-(tmp.rc==t[cc].lc)};
			else t[cc]=tmp;
		u=tree[tree[u].top].pre;
	}
	if (tree[u].deep>tree[v].deep) swap(u,v); else cc^=1;
	tmp=query(1,1,n,tree[u].tid,tree[v].tid);
	if (t[cc].rc) t[cc]={0,tmp.lc,t[cc].rc,tmp.numc+t[cc].numc-(tmp.rc==t[cc].lc)};
		else t[cc]=tmp;
	return t[0].numc+t[1].numc-(t[0].lc==t[1].lc);
}
int main(){
	n=read(),m=read();
	for (int i=1;i<=n;i++)
		col[i]=read();
	int x,y; Ecnt=0;
	for (int i=1;i<n;i++){
		x=read(),y=read();
		add(x,y),add(y,x);
	}
	build(1,0,0);
	cnt=0; getlink(1,1);
	for (int i=1;i<=n;i++)
		update(1,1,n,tree[i].tid,tree[i].tid,col[i]);
	char opt[3];int color;
	while (m--){
		scanf("%s",opt);
		if (opt[0]=='C'){
			x=read(),y=read(),color=read();
			change(x,y,color);
		}	else{
			x=read(),y=read();
			printf("%d\n",answer(x,y));
		}
	}
	return 0;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值