BZOJ 2243(树链剖分+线段树 解法)

        

2243: [SDOI2011]染色

Time Limit: 20 Sec  Memory Limit: 512 MB
Submit: 7107  Solved: 2659
[ Submit][ Status][ Discuss]

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]之间。


        我之前也写过博客说过了,这题是裸的树链剖分,但是毕竟我自己写树链剖分写得不多,于是便来练练手,然后呵呵……

        然后之前好像写挖了个坑,说没有讲清楚树链剖分的修改和查询,然后正好趁这个机会把树链剖分给理一理咯。

        不知道我之前有没有讲过,树链剖分的精髓就在于把每一个节点进行了标号,并且设置了一个top数组表示所在链的链头。这样子就可以很好的把树上的路径分割成一个个的小链,但是不至于分成一个个的点使得退化成朴素算法。然后对于每一个小链内的节点,由于所标的号是连续的,所以我们可以用线段树高效的维护它的性质。同时,再利用加和的性质来实现树上路径的查询,而这个加和时要注意一些细节,这个将是本文所讲的重点。

        树链剖分的两个dfs在这里我就不再说了,要看就去看之前的博客吧。先说说路径的修改。对于路径的修改change(x,y),当x和y不输于同一条链时,就一直往上走(直接跳到当前链的链头),并且在每一次往上走时,直接修改链头到该点所有点(序号是连续的)。做完这些后,x、y已经在同一条链中了,然后就可以直接修改x到y这连续的一段。所以说,就是这样一段一段、一链一链的修改。具体见代码及注释:

inline void change(int u,int v,int color)
{
	int tp1=top[u],tp2=top[v];			//top表示相应的链头
	while (tp1!=tp2)
	{
		if (dep[tp1]<dep[tp2]) 			//当然是让深度大的往上走
		{
			swap(u,v);
			swap(tp1,tp2);
		}
		modify(1,id[tp1],id[u],color);		//线段树修改u到tp1这一段
		u=fa[tp1]; tp1=top[u];			//往上走,到下一条链
	}
	if (dep[u]>dep[v]) swap(u,v);
	modify(1,id[u],id[v],color);			//在同一条链中,直接修改
}

        然后再说查询的操作,这个对于不同的题目有不同的写法,然后这里,我就以BZOJ 2243为例吧,道理都是类似的。

        本题由于是统计颜色段数,所以在两段合并的时候要判断交界处的颜色,如果相同,那么就是两段数量之和减一,否则不用减。那么,我们怎么判断两段的交接呢?其实就是在线段树统计的时候记录下之前链的左右(头尾)端点,然后判断当前链尾是否与之前的链头颜色相等,相等则减一,否则不减。具体来说看下图的情况(图丑勿喷),假如我

                                        

们要查询3到6的路径,那么我们先从3往上走,查询了3所在的链(只有他自己),然后再查询2到1的链,这时候我们会判断1、2链的尾与之前3链的头是否颜色相同,正好达到了想要的效果。然后弄到同一条链上之后,退出循环,直接修改,也要注意再次判断连接处的颜色情况,这里就不再特别说明了。具体看代码:

inline long long getsum(int u,int v)
{
	int tp1=top[u],tp2=top[v],c1=-1,c2=-1;
	long long ans=0;
	while (tp1!=tp2)
	{
		if (dep[tp1]<dep[tp2]) 
		{
			swap(u,v);
			swap(c1,c2);				//c1、c2为当前点的颜色
			swap(tp1,tp2);
		}
		ans+=query(1,id[tp1],id[u],id[tp1],id[u]);
		if (c1==Rc) ans--; c1=Lc;			//判断当前链尾与之前链头颜色情况
		u=fa[tp1]; tp1=top[u];
	}
	if (dep[u]>dep[v]) 
	{
		swap(u,v); swap(c1,c2);
	}
	ans+=query(1,id[u],id[v],id[u],id[v]);			//走到同一条链上后之间线段树统计
	if (Lc==c1) ans--;					//判断连接处颜色情况
	if (Rc==c2) ans--;
	return ans;
}

        好了,此题大概就这样了,线段树部分我就不再想说什么了,你再写错我也没有办法咯~线段树部分我可是一次写过后没有改的。好的,填坑完毕,最后在贴上代码吧(别忘了线段树开的空间是点数的3~4倍):

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstdlib>
#include<cstring>
#include<cmath>
#include<iomanip>
#include<vector>
#include<queue>
#define file(x) freopen(#x".in","r",stdin),freopen(#x".out","w",stdout);
#define MAX_V 100100
using namespace std;

struct Node
{
	int l,r,lcol,rcol,sum,lazy;
} tree[MAX_V];

vector<int> g[MAX_V];

int id[MAX_V],Rank[MAX_V],top[MAX_V],son[MAX_V],size[MAX_V],fa[MAX_V],dep[MAX_V];
int col[MAX_V],num,n,m,Rc,Lc;

inline void dfs1(int u,int d)
{
	dep[u]=d;
	size[u]=1;
	for(int i=0;i<g[u].size();i++)
		if (!size[g[u][i]])
		{
			fa[g[u][i]]=u;
			dfs1(g[u][i],d+1);
			size[u]+=size[g[u][i]];
			if (size[g[u][i]]>size[son[u]]) son[u]=g[u][i];
		}
}

inline void dfs2(int u,int f)
{
	top[u]=f;
	id[u]=++num;
	Rank[id[u]]=u;
	if (son[u]) dfs2(son[u],f);
	for(int i=0;i<g[u].size();i++)
		if (g[u][i]!=son[u]&&!id[g[u][i]]) dfs2(g[u][i],g[u][i]);
}

inline void push_down(int i)
{
	tree[i<<1].lazy=tree[i<<1].lcol=tree[i<<1].rcol=tree[i].lazy;
	tree[i<<1|1].lazy=tree[i<<1|1].lcol=tree[i<<1|1].rcol=tree[i].lazy;
	tree[i<<1].sum=tree[i<<1|1].sum=1; tree[i].lazy=0;
}

inline void push_up(int i)
{
	tree[i].sum=tree[i<<1].sum+tree[i<<1|1].sum;
	if (tree[i<<1].rcol==tree[i<<1|1].lcol)
		if (tree[i<<1].rcol) tree[i].sum--;
	tree[i].lcol=tree[i<<1].lcol;
	tree[i].rcol=tree[i<<1|1].rcol;
}

inline void build(int i,int l,int r)
{
	tree[i].l=l;
	tree[i].r=r;
	if (l==r)
	{
		tree[i].lcol=tree[i].rcol=col[Rank[l]];
		tree[i].sum=1; return;
	}
	int mid=(l+r)>>1;
	build(i<<1,l,mid);
	build(i<<1|1,mid+1,r);
	push_up(i);
}

inline void modify(int i,int l,int r,int color)
{
	if (tree[i].l==l&&tree[i].r==r)
	{
		tree[i].lcol=tree[i].rcol=tree[i].lazy=color;
		tree[i].sum=1; return;
	}
	if (tree[i].lazy) push_down(i);
	int mid=(tree[i].l+tree[i].r)>>1;
	if (r<=mid) modify(i<<1,l,r,color);
	else if (l>mid) modify(i<<1|1,l,r,color);
	else 
	{
		modify(i<<1,l,mid,color);
		modify(i<<1|1,mid+1,r,color);
	}
	push_up(i);
}

inline long long query(int i,int l,int r,int L,int R)
{
	if (tree[i].l==L) Lc=tree[i].lcol;
	if (tree[i].r==R) Rc=tree[i].rcol;
	if (tree[i].l==l&&tree[i].r==r) return tree[i].sum;
	if (tree[i].lazy) push_down(i);
	int mid=(tree[i].l+tree[i].r)>>1;
	if (r<=mid) return query(i<<1,l,r,L,R);
	else if (l>mid) return query(i<<1|1,l,r,L,R);
	else 
	{
		int ans=query(i<<1,l,mid,L,R)+query(i<<1|1,mid+1,r,L,R);
		if (tree[i<<1].rcol==tree[i<<1|1].lcol)
			if (tree[i<<1].rcol) return ans-1;
		return ans;
	}
	push_up(i);
}

inline void change(int u,int v,int color)
{
	int tp1=top[u],tp2=top[v];
	while (tp1!=tp2)
	{
		if (dep[tp1]<dep[tp2]) 
		{
			swap(u,v);
			swap(tp1,tp2);
		}
		modify(1,id[tp1],id[u],color);
		u=fa[tp1]; tp1=top[u];
	}
	if (dep[u]>dep[v]) swap(u,v);
	modify(1,id[u],id[v],color);
}

inline long long getsum(int u,int v)
{
	int tp1=top[u],tp2=top[v],c1=-1,c2=-1;
	long long ans=0;
	while (tp1!=tp2)
	{
		if (dep[tp1]<dep[tp2]) 
		{
			swap(u,v);
			swap(c1,c2);
			swap(tp1,tp2);
		}
		ans+=query(1,id[tp1],id[u],id[tp1],id[u]);
		if (c1==Rc) ans--; c1=Lc;
		u=fa[tp1]; tp1=top[u];
	}
	if (dep[u]>dep[v]) 
	{
		swap(u,v); swap(c1,c2);
	}
	ans+=query(1,id[u],id[v],id[u],id[v]);
	if (Lc==c1) ans--;
	if (Rc==c2) ans--;
	return ans;
}

int main()
{
	num=0;
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++)
		scanf("%d",&col[i]);
	for(int i=1;i<n;i++)
	{
		int x,y;
		scanf("%d%d",&x,&y);
		g[x].push_back(y);
		g[y].push_back(x);
	}
	dfs1(1,1);
	dfs2(1,1);
	fa[1]=1;
	build(1,1,n);
	for(int i=1;i<=m;i++)
	{
		char ch; int x,y,z;
		scanf("%s%d%d",&ch,&x,&y);
		if (ch=='C') 
		{
			scanf("%d",&z);
			change(x,y,z);
		} else printf("%lld\n",getsum(x,y));
	}
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值