BZOJ1036 [ZJOI2008]树的统计Count(树链剖分)

这篇blog算是对链剖的笔记吧


先放下题:

一棵树上有n个节点,编号分别为1到n,每个节点都有一个权值w。我们将以下面的形式来要求你对这棵树完成一些操作: I. CHANGE u t : 把结点u的权值改为t II. QMAX u v: 询问从点u到点v的路径上的节点的最大权值 III. QSUM u v: 询问从点u到点v的路径上的节点的权值和。注意:从点u到点v的路径上的节点包括u和v本身1<=n<=30000,0<=q<=200000,每个节点的权值w在-30000到30000之间。


写写我对树链剖分的一点体会:


1. 可以在给定的一棵树上动态地修改点/边的信息(如修改权值,改变权值正负等),O(log(n))
2. 求出一条树链上的信息(如sum,max,min等),O(log(n))
3. 核心:
   轻重链剖分:使一条重链上的点/边对应线段树上一段连续的区间,且自上到下对应的下标越来越大 

   线段树维护信息:可利用懒标记 


放代码

#include<stdio.h>
#include<stdlib.h>
#define INF 100000000
int u[60005]={0},v[60005]={0},first[30005]={0},next[60005]={0};
int fa[30005]={0},deep[30005]={0},size[30005]={0},son[30005]={0},top[30005]={0},pos[30005]={0};
//fa:父节点,deep:深度(0开始),size:后代数(含自身),son:重孩子,top:所在树链头,pos:此点在线段树中序号 
int sumv[150000]={0},maxv[150000]={0};
int n,e=0,tot=0;
int max(int a,int b)
{
	if(a>b) return a;
	return b;
}
void jh(int* a,int* b)
{
	int t=*a;
	*a=*b;
	*b=t;
}
void tj(int x,int y)
{
	u[++e]=x;
	v[e]=y;
	next[e]=first[x];
	first[x]=e;
}
void dfs1(int x,int pre,int dep)//求:fa,deep,size,son
{
	int i;
	fa[x]=pre;
	deep[x]=dep;
	size[x]=1;
	for(i=first[x];i!=0;i=next[i])
		if(v[i]!=pre)
		{
			dfs1(v[i],x,dep+1);
			size[x]+=size[v[i]];
			if(son[x]==0||size[son[x]]<size[v[i]]) son[x]=v[i];
		}
}
void dfs2(int x,int t)////求:top,pos,t:x所属的重链链头 
{
	int i;
	top[x]=t;
	pos[x]=++tot;
	if(son[x]!=0) dfs2(son[x],t);
	for(i=first[x];i!=0;i=next[i])
		if(v[i]!=fa[x]&&v[i]!=son[x]) dfs2(v[i],v[i]);
}
void xg(int p,int x,int o,int left,int right)//单点修改 
{
	int mid=(left+right)/2;
	if(left==right)
	{
		sumv[o]=maxv[o]=p;
		return;
	}
	if(x<=mid) xg(p,x,o*2,left,mid);
	if(x>mid) xg(p,x,o*2+1,mid+1,right);
	sumv[o]=sumv[o*2]+sumv[o*2+1];
	maxv[o]=max(maxv[o*2],maxv[o*2+1]);
}
int cx_max(int x,int y,int o,int left,int right)
{
	int mid=(left+right)/2,ans=-INF;
	if(x<=left&&right<=y) return maxv[o];
	if(x<=mid) ans=max(ans,cx_max(x,y,o*2,left,mid));
	if(y>mid) ans=max(ans,cx_max(x,y,o*2+1,mid+1,right));
	return ans;
}
int cx_sum(int x,int y,int o,int left,int right)
{
	int mid=(left+right)/2,ans=0;
	if(x<=left&&right<=y) return sumv[o];
	if(x<=mid) ans+=cx_sum(x,y,o*2,left,mid);
	if(y>mid) ans+=cx_sum(x,y,o*2+1,mid+1,right);
	return ans;
}
int getmax(int x,int y)
{
	int tx=top[x],ty=top[y],ans=-INF;
	while(tx!=ty)//x,y不在同一重链上 
	{
		if(deep[tx]<deep[ty])
		{
			jh(&x,&y);
			jh(&tx,&ty);
		}
		ans=max(ans,cx_max(pos[tx],pos[x],1,1,n));
		x=fa[tx];
		tx=top[x];
	}
	if(deep[x]>deep[y]) jh(&x,&y);
	return max(ans,cx_max(pos[x],pos[y],1,1,n));//因为同一重链上,deep小的pos小 
}
int getsum(int x,int y)
{
	int tx=top[x],ty=top[y],ans=0;
	while(tx!=ty)
	{
		if(deep[tx]<deep[ty])
		{
			jh(&x,&y);
			jh(&tx,&ty);
		}
		ans+=cx_sum(pos[tx],pos[x],1,1,n);
		x=fa[tx];
		tx=top[x];
	}
	if(deep[x]>deep[y]) jh(&x,&y);
	return ans+cx_sum(pos[x],pos[y],1,1,n);
}
int main()
{
	char s[20]={0};
	int Q,i,x,y;
	scanf("%d",&n);
	for(i=1;i<n;i++)
	{
		scanf("%d%d",&x,&y);
		tj(x,y);
		tj(y,x);
	}
	dfs1(1,0,0);
	dfs2(1,1);
	for(i=1;i<=n;i++)
	{
		scanf("%d",&x);
		xg(x,pos[i],1,1,n);
	}
	scanf("%d\n",&Q);
	for(;Q>0;Q--)
	{
		scanf("%s%d%d",s,&x,&y);
		if(s[1]=='H') xg(y,pos[x],1,1,n);//CHANGE u t : 把结点u的权值改为t
		if(s[1]=='M') printf("%d\n",getmax(x,y));//QMAX u v: 询问从点u到点v的路径上的节点的最大权值 
		if(s[1]=='S') printf("%d\n",getsum(x,y));//QSUM u v: 询问从点u到点v的路径上的节点的权值和 
	}
	return 0;
}


你可能感兴趣的:(树链剖分,bzoj)