牛客练习赛61 F苹果树题解
一.闲话
学了淀粉质有一年多了,期间基本没用过,又因为当时是直接背的代码,导致打比赛的时候对这道题根本无从下手,甚至都没想到是淀粉质qwq
然后,今天去学了一下淀粉质,发现原理和实现特别简单,快速码了个模板后,又去顺带学了下点分树,就来搞这道题了。。。
二.题解
这道题,我们看数据范围,发现成熟度的大小非常的小,所以,我们考虑下如何计算答案。
假设数据随机,我们把树减号后,期望树高是logn。
这样,我们可以,对于每一个点,我们算出他的子树里面所有点到这个子树的距离。复杂度\(O(nlogn)\)【每个点最多只有logn个祖先,所以每个点最多被算logn次】
然后,求出这个之后,我们就可以开一个动态加点线段树来做了,我们对每个点i开一点动态加点线段树,里面每个节点表示的是,i的子树中,成熟度在l-r的距离i最小的节点到i的距离
同样使用上面的证明方法,每个点最多被放进logn棵线段树中,所以,加上线段树的复杂度就是:\(O(nlog^2n)\)
再考虑1操作,其实和上面存值的操作一样的。
关于2操作,我们可以求出u的所有祖先(包括u)的子树中,成熟度为l-r的点到这个点的最小距离(访问一遍线段树即可),然后加上u到这个点的距离就行了,最后所有答案取最小再乘2(还要走回去)就是我们要求的答案了~
可能有些人有点疑惑,假如,我们对于u的一个祖先x,他的子树中成熟度符合条件的到u距离最小的点到x的路径中是有可能会走到u到x的路径的一部分的,这样的话,我们计算的时候,那部分不就重复计算了吗?但是,画个图我们就会发现,其实如果我们有部分走重了话那么,我们计算既是x的儿子,又是u的祖先的那个点y时,我们就少走了一段y到x的距离,所以,我们在算y的时候的答案就会算x的时候小,同理,如果y也有重叠的话,另一个点z就会比y算的小。。。由于我们是取最小,所以最终我们的答案一定没有重叠部分
然而,我们的做法有一个前提,就是数据随机。
如果给出一个链的话,复杂度就会原地爆炸
怎么办呢?
这里,就要用到一个神奇的东西了——点分树
点分树是什么东西?
我们先把原来的树的重心x做为根,在把x的各个儿子所在的子树的重心作为x的儿子。。。一直递归下去,最终弄出来的树就是点分树。
点分树很神奇,因为我们是把子树的重心作为根,所以,最终点分树的树高一定是logn级别的
而此题,我们其实并不需要知道我们诸如谁是谁的父亲之类的信息,我们需要知道的只是一个点到其子树所有点的距离,而这个东西,我们是可以在构造点分树时,一遍dfs直接求出来,所以我们可以建一个树高为logn的点分树,再用之前的方法,构造动态开点线段树就可以解决此题了~
代码:
#pragma GCC optimize(3,"inline","Ofast")
#include<bits/stdc++.h>
using namespace std;
const int N=1e5+1,inf=2e9,M=1e7+1;
struct node{
int v,w,nex;
}t[N<<1];
struct zxs{
int lson,rson,w;
}p[M];
int dis[N][30],dep[N];//i到深度为j的祖先的距离
int fa[N];
int val[N],siz[N],tot,root;
int w[N],las[N],len;
bool vis[N];
int id[N];
inline void add(int u,int v,int w){
t[++len]=(node){v,w,las[u]},las[u]=len;
}
inline void find_zx(int now,int fu){
siz[now]=1;val[now]=0;
for(int i=las[now];i;i=t[i].nex){
int v=t[i].v;
if(!vis[v]&&fu!=v){
find_zx(v,now);
siz[now]+=siz[v];
val[now]=max(val[now],siz[v]);
}
}
val[now]=max(val[now],tot-siz[now]);
if(val[now]<val[root]){
root=now;
}
}
int num;
inline void find_dis(int now,int fu,int Dis){
dis[now][num]=Dis;
for(int i=las[now];i;i=t[i].nex){
int v=t[i].v;
if(vis[v]||v==fu){
continue;
}
find_dis(v,now,Dis+t[i].w);
}
}
inline void work(int now,int fu){
vis[now]=1;fa[now]=fu;num=dep[now];
find_dis(now,now,0);
for(int i=las[now];i;i=t[i].nex){
int v=t[i].v;
if(vis[v]){
continue;
}
root=0;tot=siz[v],find_zx(v,v);
dep[root]=dep[now]+1;
work(root,now);
}
}
int cnt;
inline void insert(int &now,int l,int r,int x,int y){
if(!now){
now=++cnt;p[now].w=inf;
}
p[now].w=min(p[now].w,y);
if(l==r){
return;
}
int mid=(l+r)>>1;
if(x<=mid){
insert(p[now].lson,l,mid,x,y);
}else{
insert(p[now].rson,mid+1,r,x,y);
}
}
inline void Insert(int x,int val){
int now=x;
while(now){
insert(id[now],1,10000,val,dis[x][dep[now]]),now=fa[now];
}
}
inline int find(int now,int l,int r,int lc,int rc){
if(!now){
return inf;
}
if(lc<=l&&r<=rc){
return p[now].w;
}
int mid=(l+r)>>1,res=inf;
if(lc<=mid){
res=min(res,find(p[now].lson,l,mid,lc,rc));
}
if(rc>mid){
res=min(res,find(p[now].rson,mid+1,r,lc,rc));
}
return res;
}
inline int Find(int x,int l,int r){
int now=x,ans=inf;
while(now){
ans=min(ans,find(id[now],1,10000,l,r)+dis[x][dep[now]]),now=fa[now];
}
return ans>=inf?-1:ans;
}
inline int read(){
char ch=getchar();int w=0,ss=0;
while(ch<'0'||ch>'9')w|=(ch=='-'),ch=getchar();
while(ch>='0'&&ch<='9')ss=(ss*10+(ch-'0')),ch=getchar();
return w?-ss:ss;
}
inline void write(int x){
if(x<0)putchar('-'),x=-x;
if(x>9)write(x/10);
putchar(char(x%10+'0'));
}
int main(){
int n=read(),m=read();
for(int i=1;i<=n;++i){
w[i]=read();
}
for(int i=1;i<n;++i){
int u=read(),v=read(),w=read();
add(u,v,w),add(v,u,w);
}
tot=n;
val[root]=inf;
find_zx(1,1);
work(root,0);
for(int i=1;i<=n;++i){
Insert(i,w[i]);
}
while(m--){
int opt=read();
if(opt==1){
int u=read(),x=read();
Insert(u,x);
}else{
int u=read(),x=read(),y=read();
int res=Find(u,x,y);
if(res==-1){
puts("-1");
}else{
write(res*2),putchar('\n');
}
}
}
return 0;
}