简介

  动态树分治整体上由点分治发展而来。

  点分治是统计树上路径,而动态树分治用来统计与点有关的树上路径,比如多次询问某一些点到询问点的距离和。

  前置知识就是点分治。

做法

  众所周知,点分树(点分治中重心组成的树)的深度是$O(lgn)$的。

  要统计树上的路径,等价为统计经过每个点的路径。那么就统计经过每个重心的路径

  

  拿一道题目来讲比较具体:BZOJ4012。有一棵带点权边权的树,多次询问$(u,l,r)$,表示询问点权在$[l,r]$内的点到$u$的距离和。

  比如一个重心$u$,其管辖范围为$S_u$,询问的点$q$在$v$子树内:

  

  那么应该用其它两棵子树内所有符合的点到$u$的距离和 + $q$到$u$的距离乘上其它子树内符合的点的个数,就是经过这个重心的答案。

  注意到我们需要容斥:用$u$的信息斥去$v$的信息才能得到划线部分的值,这和点分治的做法大致相同。

  方便点来说,$v$子树的重心$v'$,除了要像$u$一样记录$S_v'$内所有点到它的信息,还要记录$S_v'$内所有点到$u$的信息,也就是到点分父亲的信息。原理上是和点分治一模一样的容斥,只不过为了方便,应该把这个信息放在子树的重心记录。

  完整做法如下:

    对于点分树上的每一个重心$u$:记其管辖范围为$S_u$。

    求出$S_u$内每一个点到$u$的距离值。

    对$S_u$内每一个点按照点权由小至大排序,求出排序后距离值的前缀和。

    对$u$的每一个后继$v$,若$v$子树的重心为$v'$,求出$S_v'$内所有点到$u$的距离值,对它们按照点权由小至大排序,求出排序后距离值的前缀和。

    calc1(u,l,r)表示求$S_u$内权值为$[l,r]$的点到$u$的距离和,由于已经按照权值排序,又有前缀和,可以二分得出$l$和$r$的位置,返回前缀和。

    calc2(u,l,r)表示求$S_u$内权值为$[l,r]$的点到$u$的点分父亲的距离和,求法同上。

    size(u,l,r)表示求$S_u$内权值为$[l,r]$的点有多少个,求法同上二分。

    dis(x,y)表示求原树上$x$~$y$的距离。

    

    查询$(u,l,r)$

    对于$u$和$u$的所有点分树祖先,记为$x$,设$u$在$x$的$y$子树中:

    $ans+=dis(u,x)*[size(x,l,r)-size(y,l,r)]+calc1(x,l,r)-calc2(y,l,r)$

    

 #include <cstdio>
#include <algorithm>
#include <vector>
#define mp make_pair
using namespace std;
typedef long long ll;
typedef pair<int,int> pii;
const int N=,INF=,Bas=;
int n,q,A,a[N],h[N],tot; //权值和边
int pre[N][Bas],dep[N],dist[N]; //倍增lca 点到根节点的深度和距离
int cut[N],fa[N];//点分区块断点,点分树父亲
int size[N],mins,minu; //找重心
struct Edge{int v,next,w;}g[N*]; //树边
vector<pii> lis[N],lisf[N]; //每个重心的数据列表 每个重心相对于点分父亲的数据列表
vector<ll> sum[N],sumf[N]; //每个重心列表按权值排序后距离前缀和 每个重心相对于点分父亲的列表按权值排序后距离前缀和
inline int rd(){
int x=;
char c;
while((c=getchar())<''||c>'');
x=c-'';
while(''<=(c=getchar())&&c<='') x=x*+c-'';
return x;
}
inline int min(int x,int y){return x<y?x:y;}
inline int max(int x,int y){return x>y?x:y;}
inline void swap(int &x,int &y){int t=x;x=y;y=t;}
inline void addEdge(int u,int v,int w){
g[++tot].v=v; g[tot].w=w; g[tot].next=h[u]; h[u]=tot;
g[++tot].v=u; g[tot].w=w; g[tot].next=h[v]; h[v]=tot;
}
void predfs(int u,int fa,int Dep,int Dist){//计算11行的数组
dep[u]=Dep;
dist[u]=Dist;
pre[u][]=fa;
for(int i=;i<Bas;i++) pre[u][i]=pre[pre[u][i-]][i-];
for(int i=h[u],v;i;i=g[i].next)
if((v=g[i].v)!=fa)
predfs(v,u,Dep+,Dist+g[i].w);
}
int getlca(int a,int b){
if(dep[a]<dep[b]) swap(a,b);
for(int i=Bas-;i>=;i--)
if(dep[pre[a][i]]>=dep[b]) a=pre[a][i];
if(a==b) return a;
for(int i=Bas-;i>=;i--)
if(pre[a][i]!=pre[b][i]) a=pre[a][i],b=pre[b][i];
return pre[a][];
}
int getdis(int x,int y){
int lca=getlca(x,y);
return dist[x]-dist[lca]+dist[y]-dist[lca];
}
void dfs1(int u,int fa){//找重心
size[u]=;
for(int i=h[u],v;i;i=g[i].next)
if(!cut[v=g[i].v]&&v!=fa){
dfs1(v,u);
size[u]+=size[v];
}
}
void dfs2(int u,int fa,int all){//找重心
int maxt=;
for(int i=h[u],v;i;i=g[i].next)
if(!cut[v=g[i].v]&&v!=fa){
dfs2(v,u,all);
if(size[v]>maxt) maxt=size[v];
}
if(all-size[u]>maxt) maxt=all-size[u];
if(maxt<mins) mins=maxt,minu=u;
}
int getroot(int u){//找重心
mins=INF; minu=;
dfs1(u,);
dfs2(u,,size[u]);
return minu;
}
void collect(int u,int fa,int dis,int st,int type){//收集数据至重心st的列表
if(!type) lis[st].push_back(mp(a[u],dis));
else lisf[st].push_back(mp(a[u],dis));
for(int i=h[u],v;i;i=g[i].next)
if(!cut[v=g[i].v]&&v!=fa)
collect(v,u,dis+g[i].w,st,type);
}
int find(vector<pii> &u,int x,int type){//以权值为关键字,lowerbound或upperbound x的位置
int l=,r=u.size()-,mid;
while(l<=r){
mid=(l+r)>>;
if((!type&&x<=u[mid].first)||(type&&x<u[mid].first)) r=mid-;
else l=mid+;
}
return l;
}
ll calc(vector<pii> &u,vector<ll> &s,int l,int r,int type){//计算列表在l~r的点的距离和或个数
int pos1=find(u,l,);
int pos2=find(u,r,)-;
if(pos2<pos1) return ;
if(pos1==){
if(!type) return s[min(u.size()-,pos2)];
else return min(u.size()-,pos2)+;
}
if(!type)
return s[min(u.size()-,pos2)]-s[max(,pos1-)];
else
return min(u.size()-,pos2)-max(,pos1)+;
}
int solve(int rt,int faedge){//预处理
int u=getroot(rt);
collect(u,,,u,);
sort(lis[u].begin(),lis[u].end());
ll x=;
for(int i=,sz=lis[u].size();i<sz;i++){
x+=lis[u][i].second;
sum[u].push_back(x);
}
if(faedge){
collect(rt,,faedge,u,);
sort(lisf[u].begin(),lisf[u].end());
x=;
for(int i=,sz=lisf[u].size();i<sz;i++){
x+=lisf[u][i].second;
sumf[u].push_back(x);
}
}
cut[u]=;
for(int i=h[u],v;i;i=g[i].next)
if(!cut[v=g[i].v])
fa[solve(v,g[i].w)]=u;
return u;
}
ll query(int st,int u,int l,int r,ll sonsum,int sonsize){//询问
ll ret=;
if(fa[u]) ret=query(st,fa[u],l,r,calc(lisf[u],sumf[u],l,r,),calc(lisf[u],sumf[u],l,r,));
ret+=1LL*getdis(st,u)*(calc(lis[u],sum[u],l,r,)-sonsize)+calc(lis[u],sum[u],l,r,)-sonsum;
return ret;
}
int main(){
n=rd(); q=rd(); A=rd();
for(int i=;i<=n;i++) a[i]=rd();
for(int i=,u,v,w;i<n;i++){
u=rd();v=rd();w=rd();
addEdge(u,v,w);
}
predfs(,,,);
solve(,);
int u,l,r;
ll ans=;
while(q--){
u=rd(); l=rd(); r=rd();
l=(l+ans)%A; r=(r+ans)%A;
if(l>r) swap(l,r);
ans=query(u,u,l,r,,);
printf("%lld\n",ans);
}
return ;
}

 

【Learning】 动态树分治的更多相关文章

  1. COJ 0346 WZJ的旅行(二)更新动态树分治版本

    WZJ的旅行(二) 难度级别:D: 运行时间限制:3000ms: 运行空间限制:51200KB: 代码长度限制:2000000B 试题描述 时隔多日,WZJ又来到了幻想国旅行.幻想国由N个城市组成,由 ...

  2. 【xsy2818】 最近点 动态树分治+可持久化线段树

    题目大意:给你一颗n个节点的树,最初点集S为空. 有m次操作:往当前点集S中加入/删除一个点,询问点x至集合S中任意点的最小距离,回到第t次修改点集的操作后的状态. 数据范围:$n,m≤10^5$ 我 ...

  3. 【BZOJ3730】震波 动态树分治+线段树

    [BZOJ3730]震波 Description 在一片土地上有N个城市,通过N-1条无向边互相连接,形成一棵树的结构,相邻两个城市的距离为1,其中第i个城市的价值为value[i].不幸的是,这片土 ...

  4. 点分治Day2 动态树分治

    蒟蒻Ez3real冬令营爆炸之后滚回来更新blog... 我们看一道题 bzoj3924 ZJOI2015D1T1 幻想乡战略游戏 给一棵$n$个点的树$(n \leqslant 150000)$ 点 ...

  5. 洛谷P4719 【模板】"动态 DP"&动态树分治

    [模板]"动态 DP"&动态树分治 第一道动态\(DP\)的题,只会用树剖来做,全局平衡二叉树什么的就以后再学吧 所谓动态\(DP\),就是在原本的\(DP\)求解的问题上 ...

  6. 【BZOJ4372】烁烁的游戏 动态树分治+线段树

    [BZOJ4372]烁烁的游戏 Description 背景:烁烁很喜欢爬树,这吓坏了树上的皮皮鼠.题意:给定一颗n个节点的树,边权均为1,初始树上没有皮皮鼠.烁烁他每次会跳到一个节点u,把周围与他距 ...

  7. 【BZOJ4317】Atm的树 动态树分治+二分+线段树

    [BZOJ4317]Atm的树 Description Atm有一段时间在虐qtree的题目,于是,他满脑子都是tree,tree,tree…… 于是,一天晚上他梦到自己被关在了一个有根树中,每条路径 ...

  8. 【BZOJ1095】[ZJOI2007]Hide 捉迷藏 动态树分治+堆

    [BZOJ1095][ZJOI2007]Hide 捉迷藏 Description 捉迷藏 Jiajia和Wind是一对恩爱的夫妻,并且他们有很多孩子.某天,Jiajia.Wind和孩子们决定在家里玩捉 ...

  9. 【BZOJ3924】[Zjoi2015]幻想乡战略游戏 动态树分治

    [BZOJ3924][Zjoi2015]幻想乡战略游戏 Description 傲娇少女幽香正在玩一个非常有趣的战略类游戏,本来这个游戏的地图其实还不算太大,幽香还能管得过来,但是不知道为什么现在的网 ...

随机推荐

  1. 《剑指offer》数组中出现次数超过数组长度一半的数字

    题目: 数组中有一个数字出现的次数超过数组长度的一半,请找出这个数字.例如输入一个长度为9的数组{1,2,3,2,2,2,5,4,2}.由于数字2在数组中出现了5次,超过数组长度的一半,因此输出2.如 ...

  2. 仿百度糯米TP5项目笔记

    需求分析 系统三大模块 商家平台.主平台.前台模块 Thinkphp5.0实战 仿百度糯米开发多商家电商平台网盘下载 (2017-04-24 01:46:23) 转载▼     第1章 课程简介 本章 ...

  3. Git多帐号配置,管理多个SSH

    查看自己所有的SSH-Key $ cd ~/.ssh $ ls id_rsa id_rsa.pub known_hosts 如果你已经创建过git账号那你可能和我一样会看到只有一个SSH-Key, 这 ...

  4. jQuery动画详解

    本文最初发表于博客园,并在GitHub上持续更新前端的系列文章.欢迎在GitHub上关注我,一起入门和进阶前端. 以下是正文. jQuery 动画 jQuery提供的一组网页中常见的动画效果,这些动画 ...

  5. Linux系统中常用操作命令

    常用指令 ls        显示文件或目录     -l          列出文件详细信息l(list)     -a         列出当前目录下所有文件及目录,包括隐藏的a(all)mkdi ...

  6. java存放数据的5个地方

    1.寄存器:最快的存储区,位于处理器内部,但是寄存器的数量极其有限,所以寄存器根据需求进行分配,你不 能直接控制,也不能在程序中感觉到寄存器存在的任何迹象.(C/C+允许向寄存器建议寄存器配, 但它不 ...

  7. 一个简单的node.js服务

    var http = require('http'); var qs = require('querystring'); var server = http.createServer(function ...

  8. windows下cmd常用

    windows下cmd常用 shutdown -s -t 2------2秒后关机 加上-f选项意思是强制执行 shutdown -r -t 2------2秒后重启 加上-f选项意思是强制执行 lo ...

  9. Java垃圾回收机制[转]

    原文地址:http://blog.csdn.net/zsuguangh/article/details/6429592 综合了若干人的blog- 1. 垃圾回收的意义 在C++中,对象所占的内存在程序 ...

  10. POJ 1584 A Round Peg in a Ground Hole[判断凸包 点在多边形内]

    A Round Peg in a Ground Hole Time Limit: 1000MS   Memory Limit: 10000K Total Submissions: 6682   Acc ...