BZOJ4034:[HAOI2015]T2

6 篇文章 0 订阅
2 篇文章 0 订阅

树链剖分,理解了也就这么点事 也就写了百来行嘛

两次DFS,第一次处理出树的父子关系以及求出每个节点的子节点数,第二次处理重链与轻链。

把树分为重链和轻链,选择子节点最多的儿子继承重链,其余另开一条链。

一条链上的点在线段树中分配连续的位置,方便求和等操作。

参考了下黄学长的代码。。

查了几个小时结果是因为一个类型打错了差点弄出神经病。

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<iostream>
#include<cmath>
#define maxn 100010
#define maxe 200100
#define ll long long
using namespace std;
struct data{
	int v,next;
}e[maxe];
int n,m,ed,sz;
int belong[maxn],far[maxn],head[maxn],v[maxn],size[maxn],fa[maxn];
int pos[maxn];
ll tree[maxe*2],sum[maxe*2];
int read()
{
	char ch=getchar();
	int x=0,f=1;
	while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
	while(ch>='0'&&ch<='9'){x=10*x+ch-'0';ch=getchar();}
	return x*f;
}
void insert(int x,int y){
	e[++ed].v=y;e[ed].next=head[x];head[x]=ed;
	e[++ed].v=x;e[ed].next=head[y];head[y]=ed;
}
void init()
{
	n=read();m=read();
	for(int i=1;i<=n;++i)v[i]=read();
	for(int i=1;i<n;++i){
		int x=read();int y=read();
		insert(x,y);
	}
}
void dfs1(int x)
{
	size[x]=1;      //求每一个节点的子节点数,即每一个节点的"重量"
	for(int i=head[x];i;i=e[i].next)
		if(e[i].v!=fa[x])
		{
			fa[e[i].v]=x;
			dfs1(e[i].v);
			size[x]+=size[e[i].v];
			far[x]=max(far[x],far[e[i].v]); 
		}
}
void dfs2(int x,int lian)
{
	belong[x]=lian;
	pos[x]=far[x]=++sz;
	int k=0;
	
	for(int i=head[x];i;i=e[i].next)
	{
		if(e[i].v!=fa[x]&&size[k]<size[e[i].v])k=e[i].v;     //选择最"重"的子结点
	}
	if(k)
	{
		dfs2(k,lian);far[x]=max(far[x],far[k]);    /**继承重链**/
	}        //far[x]意为以x为父节点的最远的节点编号,在线段树中方便2号修改
	for(int i=head[x];i;i=e[i].next)
	{
		if(e[i].v!=fa[x]&&k!=e[i].v)
		{
			dfs2(e[i].v,e[i].v);    //另开新链
			far[x]=max(far[x],far[e[i].v]);
		}
	}
}
void push(int t,int l,int r)
{
	if(l==r)return;
	ll x=tree[t];
	tree[t]=0;
	int mid=(l+r)>>1;
	tree[t<<1]+=x;tree[t<<1|1]+=x;
	sum[t<<1]+=x*(mid-l+1);
	sum[t<<1|1]+=x*(r-mid);
}
void add(int t,int l,int r,int x,int y,ll num)
{
	if(tree[t])push(t,l,r);
	if(l==x&&r==y)
	{
		tree[t]+=num;sum[t]+=num*(r-l+1);return;
	}
	int mid=(l+r)>>1;
	if(x<=mid)add(t<<1,l,mid,x,min(mid,y),num);
	if(y>mid)add(t<<1|1,mid+1,r,max(mid+1,x),y,num);
	sum[t]=sum[t<<1]+sum[t<<1|1];
}
ll q(int t,int l,int r,int x,int y)
{
	if(tree[t])push(t,l,r);
	if(l==x&&r==y)return sum[t];
	int mid=(l+r)>>1;
	ll ans=0;
	if(x<=mid)ans+=q(t<<1,l,mid,x,min(mid,y));
	if(y>mid)ans+=q(t<<1|1,mid+1,r,max(x,mid+1),y);
	return ans;
}
ll q(int x)
{
	ll ans=0;
	while(belong[x]!=1)    //如果没有到根节点所在的重链
	{
		ans+=q(1,1,n,pos[belong[x]],pos[x]);       //求这条链上的和
		x=fa[belong[x]];        //x跳转到该链的父节点继续操作
	}
	ans+=q(1,1,n,1,pos[x]);
	return ans;
}
void solve()
{
	
	for(int i=1;i<=m;++i)
	{
		int pr=read(),x=read();
		if(pr==1){int a=read();add(1,1,n,pos[x],pos[x],a);}
		else
		if(pr==2){int a=read();add(1,1,n,pos[x],far[x],a);}
		else
		{printf("%lld\n",q(x));}
	}
}
int main()
{
	init();
	dfs1(1);
	dfs2(1,1);
	for(int i=1;i<=n;++i)add(1,1,n,pos[i],pos[i],v[i]);
	solve();
	
	return 0;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值