bzoj4372 烁烁的游戏 动态点分治+线段树

90 篇文章 0 订阅
3 篇文章 0 订阅

Description


背景:烁烁很喜欢爬树,这吓坏了树上的皮皮鼠。

给定一颗n个节点的树,边权均为1,初始树上没有皮皮鼠。
烁烁他每次会跳到一个节点u,把周围与他距离不超过d的节点各吸引出w只皮皮鼠。皮皮鼠会被烁烁吸引,所以会一直待在节点上不动。
烁烁很好奇,在当前时刻,节点u有多少个他的好朋友—皮皮鼠。
大意:
给一颗n个节点的树,边权均为1,初始点权均为0,m次操作:
Q x:询问x的点权。
M x d w:将树上与节点x距离不超过d的节点的点权均加上w。

n,m<=105,|w|<=104
注意:w不一定为正整数,因为烁烁可能把皮皮鼠吓傻了。

Solution


我终于会动态点分治辣

我们把点分治时下一层的分治中心作为此时分治中心的儿子,这样可以得到一棵新的树,我们称之为点分树
这棵新的树有许多优秀的性质,比如只有log层。于是一些跑不过的暴力就可以在点分树上套着数据结构跑了

在这题我们可以在每个节点x开一棵以 [x的点分树子树内到x距离] 作为下标的线段树,修改的时候就是把x到点分树的根路径上的所有线段树都改掉,查询同理。注意到直接这么做是会算重的,我们再开一棵以 [x的点分树子树内到fa[x]距离] 作为下标的线段树,这样修改的时候两棵都改,然后查询就能容斥去重了

线段树可以标记永久化,这样新建的节点少一些

需要注意的是点分树上两点距离是原树上两点的距离,且原树上的父子关系在点分树上不一定成立

跑得有点慢但是1A了

Code


#include <stdio.h>
#include <string.h>
#include <algorithm>
#define rep(i,st,ed) for (int i=st;i<=ed;++i)
#define drp(i,st,ed) for (int i=st;i>=ed;--i)

const int INF=0x3f3f3f3f;
const int N=100005;

struct treeNode {int l,r,tag;} t[N*289];
struct edge {int y,next;} e[N*2];

int dep[N],acs[N][18],fa[N],size[N],mxSon[N];
int root1[N],root2[N],tot,rec,sum;
int ls[N],edCnt,n,m;

bool del[N];

int read() {
	int x=0,v=1; char ch=getchar();
	for (;ch<'0'||ch>'9';v=(ch=='-')?(-1):(v),ch=getchar());
	for (;ch<='9'&&ch>='0';x=x*10+ch-'0',ch=getchar());
	return x*v;
}

void add_edge(int x,int y) {
	e[++edCnt]=(edge) {y,ls[x]}; ls[x]=edCnt;
	e[++edCnt]=(edge) {x,ls[y]}; ls[y]=edCnt;
}

void dfs1(int now) {
	rep(i,1,17) acs[now][i]=acs[acs[now][i-1]][i-1];
	size[now]=1;
	for (int i=ls[now];i;i=e[i].next) {
		if (e[i].y==acs[now][0]) continue;
		acs[e[i].y][0]=now; dep[e[i].y]=dep[now]+1;
		dfs1(e[i].y); size[now]+=size[e[i].y];
	}
}

void get_root(int now,int from) {
	size[now]=1; mxSon[now]=0;
	for (int i=ls[now];i;i=e[i].next) {
		if (e[i].y==from||del[e[i].y]) continue;
		get_root(e[i].y,now); size[now]+=size[e[i].y];
		mxSon[now]=std:: max(mxSon[now],size[e[i].y]);
	}
	mxSon[now]=std:: max(mxSon[now],sum-size[now]);
	if (mxSon[now]<mxSon[rec]) rec=now;
}

void build(int now) {
	for (int i=ls[now];i;i=e[i].next) {
		if (del[e[i].y]) continue;
		rec=0; sum=size[e[i].y];
		get_root(e[i].y,now);
		fa[rec]=now; del[rec]=1;
		build(rec);
	}
}

int get_lca(int x,int y) {
	if (dep[x]<dep[y]) std:: swap(x,y);
	drp(i,17,0) if (dep[acs[x][i]]>=dep[y]) x=acs[x][i];
	if (x==y) return x;
	drp(i,17,0) if (acs[x][i]!=acs[y][i]) x=acs[x][i],y=acs[y][i];
	return acs[x][0];
}

int get_dis(int x,int y) {
	return dep[x]+dep[y]-dep[get_lca(x,y)]*2;
}

void modify(int &now,int tl,int tr,int l,int r,int v) {
	if (r<l) return ;
	if (!now) now=++tot;
	if (tl>=l&&tr<=r) return (void) (t[now].tag+=v);
	int mid=(tl+tr)>>1;
	modify(t[now].l,tl,mid,l,std:: min(r,mid),v);
	modify(t[now].r,mid+1,tr,std:: max(mid+1,l),r,v);
}

int query(int now,int tl,int tr,int x) {
	if (x<tl||x>tr) return 0;
	if (tl==tr) return t[now].tag;
	int mid=(tl+tr)>>1;
	int res=t[now].tag;
	res+=query(t[now].l,tl,mid,x);
	res+=query(t[now].r,mid+1,tr,x);
	return res;
}

void change(int x,int d,int w) {
	int dis=0,st=x;
	modify(root1[x],0,n,0,d,w);
	while (fa[x]) {
		dis=get_dis(st,fa[x]);
		modify(root1[fa[x]],0,n,0,d-dis,w);
		modify(root2[x],0,n,0,d-dis,w);
		x=fa[x];
	}
}

int ask(int x) {
	int res=query(root1[x],0,n,0);
	int dis=0,st=x;
	while (fa[x]) {
		dis=get_dis(st,fa[x]);
		res+=query(root1[fa[x]],0,n,dis);
		res-=query(root2[x],0,n,dis);
		x=fa[x];
	}
	return res;
}

int main(void) {
	freopen("data.in","r",stdin);
	freopen("myp.out","w",stdout);
	n=read(),m=read(); mxSon[0]=INF;
	rep(i,2,n) add_edge(read(),read());
	dfs1(dep[1]=1);
	sum=n; rec=0; get_root(1,0);
	del[rec]=1; build(rec);
	for (char opt;m--;) {
		for (opt=getchar();opt!='M'&&opt!='Q';opt=getchar());
		int x=read();
		if (opt=='Q') printf("%d\n", ask(x));
		else {
			int d=read(),w=read();
			change(x,d,w);
		}
	}
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值