BZOJ 3070 震波 题解

题目传送门

题目大意: 有一棵 n n n 个带权点的树,所有边的长度都是 1 1 1,现在有两种操作。操作 1 1 1:询问 x x x 周围与它距离不超过 k k k 的点的权值和(包括自己);操作 $24:修改某点的权值。

题解

考虑使用动态点分治。

对于每一次询问,可以从 x x x 开始沿着点分树向上走,对于每个到达的点(也就是重心),我们可以统计答案,该点能提供的贡献就是深度小于等于 k − d e e p [ x ] k-deep[x] kdeep[x](这里的 x x x 的深度(即 d e e p [ x ] deep[x] deep[x])是相对于该重心而言的)的点的贡献和,但是还要减去与 x x x 同一棵子树内的贡献。

对于每一次修改,我们依然沿着点分树走,沿途修改对于每个重心而言深度为 d e e p [ x ] deep[x] deep[x] 的点的贡献和。

发现对于每个重心,我们有两种操作,一是统计小于等于某深度的点的权值和,一种是修改某深度的点的权值和,发现正是个改点求段操作,于是可以用树状数组愉快的解决~

还有一个坑点,就是在点分树上求解的时候,不能在发现某个时候 k − d e e p [ x ] < 0 k-deep[x]<0 kdeep[x]<0 就停下,不继续向上跳求解。因为在点分树上跳时,是不能保证重心到 x x x 的距离递增的。

具体细节看代码吧:

#include <cstdio>
#include <cstring>
#include <map>
#include <vector>
#include <algorithm>
using namespace std;
#define maxn 100010

int n,m;
int value[maxn];
struct node{int y,next;};
node e[maxn*2];
int first[maxn];
void buildroad(int x,int y)//建边
{
	static int len=0;
	e[++len]=(node){y,first[x]};
	first[x]=len;
}
struct trarr{//树状数组
	vector<int> tr;
	int l;
	trarr(){tr.clear();tr.push_back(0);l=0;}//这个push_back是用来填充数组的0位置的
	inline int lowbit(int x){return x&(-x);}
	void add(int x,int y)
	{
		for(;x<=l;x+=lowbit(x))
		tr[x]+=y;
	}
	int sum(int x)
	{
		int ans=0;
		for(;x>=1;x-=lowbit(x))
		ans+=tr[x];
		return ans;
	}
};
bool v[maxn];
int Size,size[maxn],mson[maxn],root;
void getroot(int x,int fa)//找重心
{
	size[x]=1;mson[x]=0;
	for(int i=first[x];i;i=e[i].next)
	{
		int y=e[i].y;
		if(v[y]||y==fa)continue;
		getroot(y,x);
		size[x]+=size[y];
		if(size[y]>mson[x])mson[x]=size[y];
	}
	if(Size-size[x]>mson[x])mson[x]=Size-size[x];
	if(mson[x]<mson[root])root=x;
}
vector<int>a[maxn];//a[i][j] : 以i为重心的子树中,深度为j的点的权值的总和 
int aa[maxn];//aa[i] : 以i为重心的子树的最大深度
trarr atree[maxn];//用来维护a数组的树状数组
struct wulala{int arr[maxn];};
vector<wulala> dis;//dis[i].arr[j] : 点分治第i层中,点j到重心的距离
//因为在点分治的每一层中,每个点只属于一个重心,所以可以这样表示该层中x到重心的距离
wulala newwulala;
int deepp=0;//点分治层数
void getdis_a(int x,int fa,int tr,int deep,int dep)//统计每个点到重心的距离以及以该重心为根的子树内每个深度内的点权和
{
	if(deep>aa[tr])aa[tr]++,a[tr].push_back(0);
	a[tr][deep]+=value[x];
	dis[dep].arr[x]=deep;
	for(int i=first[x];i;i=e[i].next)
	{
		int y=e[i].y;
		if(y==fa||v[y])continue;
		getdis_a(y,x,tr,deep+1,dep);
	}
}
int son[maxn];
map<int,int> s[maxn];//给每个儿子重新编号,s[x][y]表示y是x的第几个儿子
vector< vector<int> >b[maxn];//b[i][j][k] : 以i为重心的子树中,i的第j棵子树中深度为k的点权和,此时的深度 
vector<int> bb[maxn];//以x为重心的子树中,x的第y棵子树的深度 
vector<trarr> btree[maxn];//维护b的树状数组
vector<int> newvec;
void getdis_b(int x,int fa,int tr,int num,int deep)
{
	if(deep>bb[tr][num])bb[tr][num]++,b[tr][num].push_back(0);
	b[tr][num][deep]+=value[x];
	for(int i=first[x];i;i=e[i].next)
	{
		int y=e[i].y;
		if(y==fa||v[y])continue;
		getdis_b(y,x,tr,num,deep+1);
	}
}
int fa[maxn],con[maxn];
int deep_focus[maxn];//每个重心在点分树中的深度
void buildtree(int x,int deep,int ssize)
{
	if(deep>deepp)deepp++,dis.push_back(newwulala);
	v[x]=true;deep_focus[x]=deep;
	getdis_a(x,0,x,0,deep);
	for(int i=first[x];i;i=e[i].next)
	{
		int y=e[i].y;
		if(v[y])continue;
		son[x]++;//x的儿子数量
		s[x][y]=son[x];
		b[x].push_back(newvec);
		bb[x].push_back(-1);//注意,是-1不是0,结合上面代码就能理解了
		getdis_b(y,x,x,son[x],0);
		root=0;Size=size[y]<size[x]?size[y]:(ssize-size[x]);
		getroot(y,0);
		fa[root]=x;con[root]=y;
		//fa表示root的父亲(在点分树中),con表示root在fa[root]的哪一个儿子的子树中(这个儿子是指原树中的)
		buildtree(root,deep+1,(size[y]<size[x]?size[y]:(ssize-size[x])));
	}
}
void buildatree()//将a数组用树状数组atree维护起来
{
	for(int i=1;i<=n;i++)
	{
		for(int j=1;j<=aa[i];j++)
		atree[i].tr.push_back(0);
		atree[i].l=aa[i];
		for(int j=1;j<=aa[i];j++)
		atree[i].add(j,a[i][j]);
	}
}
trarr newtr;
void buildbtree()//将b数组用树状数组btree维护起来
{
	for(int i=1;i<=n;i++)
	{
		btree[i].push_back(newtr);
		for(int j=1;j<=son[i];j++)
		{
			btree[i].push_back(newtr);
			for(int k=1;k<=bb[i][j];k++)
			btree[i][j].tr.push_back(0);
			btree[i][j].l=bb[i][j];
			for(int k=1;k<=bb[i][j];k++)
			btree[i][j].add(k,b[i][j][k]);
		}
	}
}
int last=0;
void work(int x,int dist){ last+=atree[x].sum(min(dist,aa[x]))+a[x][0]; }
//work统计在以x为重心的子树中,与x的距离小于等于dist的点权和
void go(int x,int y,int dist,int point)//统计以在以x为重心的子树中,与point的距离小于等于dist的点权和
//y表示 point在x的y这个儿子的子树中
{
	int need=dist-dis[deep_focus[x]].arr[point];//need表示将要求以x为重心的子树中深度小于等于need的点权和
	if(need>=0)//如果小于0则不能产生贡献
	{
		last+=atree[x].sum(min(aa[x],need))+a[x][0];//求出点权和
		last-=btree[x][s[x][y]].sum(min(need-1,bb[x][s[x][y]]))+(need>0?b[x][s[x][y]][0]:0);//减去以y为根的子树产生的贡献
		//因为0这个位置是不能用树状数组维护的,于是我们单独考虑,change函数中同理
	}
	if(fa[x])go(fa[x],con[x],dist,point);
}
void change(int x,int y,int newval,int point)//newval表示point这个节点的新权值
{
	atree[x].add(dis[deep_focus[x]].arr[point],-value[point]);//减去原来的权值
	atree[x].add(dis[deep_focus[x]].arr[point],newval);//加上现在的权值
	if(dis[deep_focus[x]].arr[point]==1)b[x][s[x][y]][0]=newval;//单独考虑0位置
	else btree[x][s[x][y]].add(dis[deep_focus[x]].arr[point]-1,-value[point]),btree[x][s[x][y]].add(dis[deep_focus[x]].arr[point]-1,newval);
	if(fa[x])change(fa[x],con[x],newval,point);
}
//--------------------------------------------------------------
//以下为输入输出优化(不卡卡常过不了= =)
inline char cn()
{
	static char buf[1000010],*t1=buf,*t2=buf;
	return t1==t2&&(t2=(t1=buf)+fread(buf,1,1000000,stdin),t1==t2)?EOF:*t1++;
}
void read(int &x)
{
	x=0;
	char ch=cn();
	while(ch<'0'||ch>'9')ch=cn();
	while(ch>='0'&&ch<='9')x=x*10+ch-'0',ch=cn();
}
char buff[1000020];
int len=-1;
int output[20],lo;
void add(int x)
{
	lo=0;
	while(x>0)output[++lo]=x%10,x/=10;
	while(lo>0)buff[++len]=output[lo--]+'0';
	buff[++len]='\n';
	if(len>1000000)fwrite(buff,1,len,stdout),len=-1;
}
//--------------------------------------------------------------

int main()//主函数就没什么好讲的了,其中包含的函数的意义已经讲明白了(其实只是因为懒。。)
{
//	freopen("data.txt","r",stdin);
//	freopen("mine.txt","w",stdout);
	read(n);read(m);
	for(int i=1;i<=n;i++)
	read(value[i]);
	for(int i=1;i<n;i++)
	{
		int x,y;
		read(x);read(y);
		buildroad(x,y);
		buildroad(y,x);
	}
	root=0;Size=n;
	mson[0]=999999999;
	memset(aa,-1,sizeof(aa));
	for(int i=1;i<=n;i++)
	b[i].push_back(newvec),bb[i].push_back(0);
	getroot(1,0);
	dis.push_back(newwulala);
	buildtree(root,1,n);
	buildatree();
	buildbtree();
	for(int i=1;i<=m;i++)
	{
		int id,x,y;
		read(id);read(x);read(y);
		x^=last,y^=last;
		if(id==0)
		{
			last=0;
			work(x,y);
			if(fa[x])go(fa[x],con[x],y,x);
			add(last);
		}
		else
		{
			a[x][0]=y;
			if(fa[x])change(fa[x],con[x],y,x);
			value[x]=y;
		}
	}
	fwrite(buff,1,len,stdout);
}
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值