一.闲话
学了淀粉质有一年多了,期间基本没用过,又因为当时是直接背的代码,导致打比赛的时候对这道题根本无从下手,甚至都没想到是淀粉质qwq
然后,今天去学了一下淀粉质,发现原理和实现特别简单,快速码了个模板后,又去顺带学了下点分树,就来搞这道题了。。。
二.题解
这道题,我们看数据范围,发现成熟度的大小非常的小,所以,我们考虑下如何计算答案。
假设数据随机,我们把树减号后,期望树高是logn。
这样,我们可以,对于每一个点,我们算出他的子树里面所有点到这个子树的距离。复杂度【每个点最多只有logn个祖先,所以每个点最多被算logn次】
然后,求出这个之后,我们就可以开一个动态加点线段树来做了,我们对每个点i开一点动态加点线段树,里面每个节点表示的是,i的子树中,成熟度在l-r的距离i最小的节点到i的距离
同样使用上面的证明方法,每个点最多被放进logn棵线段树中,所以,加上线段树的复杂度就是:
再考虑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; }