题面

传送门

思路

观察一下题目,要求的是修改“距离点$u$的距离一定的点权值”,那这个就不能用传统的dfs序类算法+线段树维护,因为涉及到向父亲回溯的问题

看到和树上距离相关的东西,还能想到什么呢?

没错,点分治算法

然后发现本题有修改操作,那动态点分治试一试?

如何点分治?

我们先把这棵树的点分树构造出来(后面的操作都是在点分树上的了)

注意到我们一开始的dfs序想法中,影响最大的是往父亲回溯是可以达到$O(n)$的,再操作会炸

但是点分树的深度是严格$O(log_2n)$的,所以我们要是往上回溯也最多会回溯$log_2n$个节点

那么这是不是说明我们可以在点分树上开好线段树,然后每次对于当前点和其所有祖先更新信息,这样单次操作时间复杂度是$O(log_2^2n)$的,可以过掉这道题

线段树上怎么操作?

确定了线段树之后,我们发现另一个问题,那就是每次往根走的时候,因为修改点到当前枚举的它的祖先是有一个距离的,所以我们在这一点的线段树上维护时只能更新一定深度的点,而且还要去掉之前已经更新过的点......

好麻烦,而且这个去掉的部分并没有什么办法做qwq

那么怎么办呢?我们可以考虑换个线段树的定义

我们之前的所有讨论,都是基于线段树的节点代表点分树上的真实节点(不管是dfs序还是bfs序之类的),但是这道题的一个关键就是距离 ,那么我们可不可以拿距离来定义一棵线段树呢??

以距离为数组下标的线段树

我们对于每一个点开一棵动态开点线段树,定义线段树叶节点$l$,其值为$w[l]$,其代表当前节点$u$对$u$的点分树子树中距离为$l$的所有点做出的修改为$w[l]$

因为本题中所有的修改都是固定距离的,所以对同距离的点的修改可以合并在一起

那么接下来,我们来处理之前的去重问题

容易发现,现在的这棵线段树而言,我们只需要对某个点修改的同时,再新开另一棵线段树在这个点的父亲上,把当前点修改的部分记录在上面,询问的时候去掉(因为当前的定义是点分树子树中

具体而言,对于点u,设当前已经枚举到了它的祖先f1,下一个是f2,修改的权值是w,询问距离范围是d

那么对于f1,我们需要更新区间$[0,d-dis(u,f1)]$,值加上w

同时我们在f2的第二棵线段树中,更新区间$[0,d-dis(u,f1)]$,值加上w

查询的时候,我们依旧从$u$每次往上跳,对于每个祖先$f$,我们在他的两棵线段树上单点查询,每次查询位置$dis(u,f)$,用第一棵线段树的值减去第二棵的

最后,修改和询问的时候,一开始都要对当前点u处理,因为$u$没有儿子需要斥掉(这一段本质是个容斥原理)

线段树是区间修改单点查询的动态开点线段树,使用标记永久化可以方便地实现

如果还有没说清楚的,可以参考一下代码qwq

Code

说起来算法还挺复杂的.....但是代码不算长哦

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cassert>
#include<ctime>
#define ll long long
#define log lg2
using namespace std;
inline int read(){
int re=0,flag=1;char ch=getchar();
while(ch>'9'||ch<'0'){
if(ch=='-') flag=-1;
ch=getchar();
}
while(ch>='0'&&ch<='9') re=(re<<1)+(re<<3)+ch-'0',ch=getchar();
return re*flag;
}
int n,q,first[100010],cnte,son[100010],siz[100010],fa[100010],dep[100010],app[100010],sum;
int rt,log[500010],euler[500010],cntu=0,st[500010][20],back[500010];
struct edge{
int to,next;
}a[200010];
inline void add(int u,int v){
a[++cnte]=(edge){v,first[u]};first[u]=cnte;
a[++cnte]=(edge){u,first[v]};first[v]=cnte;
}
void getdep(int u,int f){
int i,v;dep[u]=dep[f]+1;euler[++cntu]=dep[u];app[u]=cntu;back[cntu]=u;
for(i=first[u];~i;i=a[i].next){
v=a[i].to;if(v==f) continue;
getdep(v,u);
euler[++cntu]=dep[u];back[cntu]=u;
}
}
void ST(){
log[1]=0;
int i,j,l,r;
for(i=2;i<=cntu;i++) log[i]=log[i>>1]+1;
for(i=1;i<=cntu;i++) st[i][0]=i;
for(j=1;j<=19;j++){
for(i=1;i+(1<<j)<=cntu;i++){
l=st[i][j-1];r=st[i+(1<<(j-1))][j-1];
st[i][j]=(euler[l]<euler[r])?l:r;
}
}
}
int lca(int l,int r){//ST表rmq实现LCA
l=app[l];r=app[r];
if(l>r) swap(l,r);
int k=log[r-l+1],x,y,re;
x=st[l][k];y=st[r-(1<<k)+1][k];
re=((euler[x]<euler[y])?x:y);
return back[re];
}
int getdis(int l,int r){
return dep[l]+dep[r]-2*dep[lca(l,r)];
} bool vis[100010]={0};
void getroot(int u,int f){
int i,v;siz[u]=1;son[u]=0;
for(i=first[u];~i;i=a[i].next){
v=a[i].to;if(vis[v]||v==f) continue;
getroot(v,u);
siz[u]+=siz[v];son[u]=max(son[u],siz[v]);
}
son[u]=max(son[u],sum-siz[u]);
if(son[rt]>son[u]) rt=u;
}
void build(int u){//建立点分树
int i,v;vis[u]=1;
for(i=first[u];~i;i=a[i].next){
v=a[i].to;if(vis[v]) continue;
rt=0;son[rt]=1e9;sum=siz[v];
getroot(v,0);fa[rt]=u;build(rt);
}
} int root[100010][2],lazy[20000010],cnt,ch[20000010][2];
void insert(int &cur,int l,int r,int ql,int qr,int val){
if(!cur) cur=++cnt;
if(l==ql&&r==qr){lazy[cur]+=val;return;}
int mid=(l+r)>>1;
if(qr<=mid) insert(ch[cur][0],l,mid,ql,qr,val);
else{
if(ql>mid) insert(ch[cur][1],mid+1,r,ql,qr,val);
else{
insert(ch[cur][0],l,mid,ql,mid,val);
insert(ch[cur][1],mid+1,r,mid+1,qr,val);
}
}
}
void update(int u,int d,int val){
int tmp=u,dis;
insert(root[u][0],0,n,0,d,val);//先搞当前点
for(;fa[u];u=fa[u]){
dis=d-getdis(tmp,fa[u]);
if(dis<0) continue;//如果距离已经大于询问的距离了,那么显然不用改了
insert(root[fa[u]][0],0,n,0,dis,val);//分别更新两棵树
insert(root[u][1],0,n,0,dis,val);
}
}
int query(int cur,int l,int r,int pos){
if(!cur) return 0;
if(l==r) return lazy[cur];
int mid=(l+r)>>1;
if(pos<=mid) return lazy[cur]+query(ch[cur][0],l,mid,pos);
else return lazy[cur]+query(ch[cur][1],mid+1,r,pos);
}
int getans(int u){
int re=query(root[u][0],0,n,0),tmp=u;//先加上当前点询问
for(;fa[u];u=fa[u]){
re+=query(root[fa[u]][0],0,n,getdis(tmp,fa[u]))-query(root[u][1],0,n,getdis(tmp,fa[u]));//两个值相减
}
return re;
}
int main(){
memset(first,-1,sizeof(first));
n=read();q=read();int i,t1,t2,t3;char s[10];
for(i=1;i<n;i++){
t1=read();t2=read();
add(t1,t2);
} getdep(1,0);ST();
son[rt]=1e9;rt=0;sum=n;
getroot(1,0);build(rt); for(i=1;i<=q;i++){
scanf("%s",s);
if(s[0]=='Q'){
t1=read();
printf("%d\n",getans(t1));
}
else{
t1=read();t2=read();t3=read();
update(t1,t2,t3);
}
}
}

[bzoj4372] 烁烁的游戏 [动态点分治+线段树+容斥原理]的更多相关文章

  1. BZOJ4372烁烁的游戏——动态点分治+线段树(点分树套线段树)

    题目描述 背景:烁烁很喜欢爬树,这吓坏了树上的皮皮鼠.题意:给定一颗n个节点的树,边权均为1,初始树上没有皮皮鼠.烁烁他每次会跳到一个节点u,把周围与他距离不超过d的节点各吸引出w只皮皮鼠.皮皮鼠会被 ...

  2. [BZOJ4372]烁烁的游戏(动态点分治+线段树)

    和[BZOJ3730]震波几乎一样,每个点建两棵线段树分别代表它的管辖范围内以它为LCA的路径的贡献和它对父亲的贡献. 注意点分树上的点的距离在原树上不单调,所以不能有若距离超出限制就break之类的 ...

  3. 【bzoj4372】烁烁的游戏 动态点分治+线段树

    题目描述 给一颗n个节点的树,边权均为1,初始点权均为0,m次操作:Q x:询问x的点权.M x d w:将树上与节点x距离不超过d的节点的点权均加上w. 输入 第一行两个正整数:n,m接下来的n-1 ...

  4. bzoj 4372: 烁烁的游戏 动态点分治_树链剖分_线段树

    [Submit][Status][Discuss] Description 背景:烁烁很喜欢爬树,这吓坏了树上的皮皮鼠. 题意: 给定一颗n个节点的树,边权均为1,初始树上没有皮皮鼠. 烁烁他每次会跳 ...

  5. BZOJ4372: 烁烁的游戏(动态点分治)

    Description 背景:烁烁很喜欢爬树,这吓坏了树上的皮皮鼠.题意:给定一颗n个节点的树,边权均为1,初始树上没有皮皮鼠.烁烁他每次会跳到一个节点u,把周围与他距离不超过d的节点各吸引出w只皮皮 ...

  6. 【loj6145】「2017 山东三轮集训 Day7」Easy 动态点分治+线段树

    题目描述 给你一棵 $n$ 个点的树,边有边权.$m$ 次询问,每次给出 $l$ .$r$ .$x$ ,求 $\text{Min}_{i=l}^r\text{dis}(i,x)$ . $n,m\le ...

  7. 【bzoj3730】震波 动态点分治+线段树

    题目描述 在一片土地上有N个城市,通过N-1条无向边互相连接,形成一棵树的结构,相邻两个城市的距离为1,其中第i个城市的价值为value[i].不幸的是,这片土地常常发生地震,并且随着时代的发展,城市 ...

  8. 2019ICPC上海网络赛 A Lightning Routing I 点分树(动态点分治)+线段树

    题意 给一颗带边权的树,有两种操作 \(C~e_i~w_i\),将第\(e_i\)条边的边权改为\(w_i\). \(Q~v_i\),询问距\(v_i\)点最远的点的距离. 分析 官方题解做法:动态维 ...

  9. BZOJ4372 烁烁的游戏(动态点分治+线段树)

    建出点分树,每个节点维护其作为点分树上lca对子树内点的贡献,线段树维护即可,同时另开一个线段树以减掉父亲重复的贡献. #include<iostream> #include<cst ...

随机推荐

  1. c++学习(一)

    基本数据类型 类型 关键字 描述 所占字节数 最大值 最小值 布尔型 bool 存储值 true 或 false bool 1 0 字符型 char 通常是一个八位字节(一个字符).这是一个整数类型 ...

  2. Q&A - 如何获取ip地址所在地

    获取其IP地址后,传入以下URL,并请求该URL,该请求会响应一个JSON格式的数据包,该IP地址的所在地均在这个数据包内   http://int.dpool.sina.com.cn/iplooku ...

  3. Oracle常用傻瓜问题1000问

    Oracle常用傻瓜问题1000问 大家在应用ORACLE的时候可能会遇到很多看起来不难的问题, 特别对新手来说, 今天我简单把它总结一下, 发布给大家, 希望对大家有帮助! 和大家一起探讨, 共同进 ...

  4. 动态代理和AOP

    之前说过了我对IOC的理解,这篇文章说以下我对动态代理和基本的对AOP的理解. 所谓动态代理就是,在运行时,动态创建实现了一组指定接口的实现类对象. 比如有: interface A { } inte ...

  5. Redis------Set集合类型

    set是string类型的无序集合 类比:你的朋友不能超过2的32次方-1个元素 基本添加删除操作 取并集 取交集 取差集 注意:每个元素的各个元素不能重复 应用场合:qq好友推荐‘ TomFri 的 ...

  6. PHP小练习题

    前几天在百度知道里面看到有位网友询问如何制作一下的小程序:用php语言设计一个小程序,计算今天到达下月的天数.全部输出这些天数,并使得每天的日期以三种颜色循环显示,设置三个表单,让用户选择字体颜色,然 ...

  7. openwrt(二) 配置openwrt及编译

    导航 1. 配置openwrt 2. 编译openwrt 3. 错误记录 1. 配置openwrt 在openwrt的根目录下,执行make menuconfig. 这个界面我也只是了解了这两个选项而 ...

  8. linux epoll用法

    epoll 是 linux 特有的 I/O 复用函数.它是把用户关心的文件描述符事件放在内核的一个事件列表中,故而,无须像select和poll一样每次调用都重复传入文件描述符或事件集.但是, epo ...

  9. MySQL之查询性能优化(三)

    MySQL查询优化器的局限性 MySQL的万能“嵌套循环”并不是对每种查询都是最优的.不过还好,MySQL查询优化只对少部分查询不适用,而且我们往往可以通过改写查询让MySQL高效地完成工作. 关联子 ...

  10. CC3200在AP模式的TCP sock作为客户端连接时返回SL_ECONNREFUSED(-111) Connection refused

    1. CC3200处于AP模式(电脑无线连接CC3200的WIFI信号),开启一个TCP socket,这个socket作为TCP客户端去连接TCP服务器端 struct sockaddr_in ad ...