[SDOI2016]游戏(树链剖分,李超线段树模板)
Posted hsez-cyx
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了[SDOI2016]游戏(树链剖分,李超线段树模板)相关的知识,希望对你有一定的参考价值。
Solution
对于一次Alice的操作,设 lca 为 s , t 的 LCA ,dis [ i ] 为点 i 到根的路径长度
则 s 到 lca 的路径上每个点 i 添加一个数字
$$-a*disleft[i ight]+(a*disleft[s ight]+b)$$
t 到 lca 的路径上每个点 i 添加一个数字
$$a*disleft[i ight]+a*(disleft[s ight]-2*disleft[lca ight])$$
可以看出树剖+李超线段树的模型
李超线段树——在平面直角坐标系中插入一些线段,维护这些线段上某整数横坐标的最大/最小纵坐标
原理
先按普通线段树建树,树上每个节点维护两个标记:
- t:完全覆盖该节点维护的区间,且使该节点维护区间中点处对应纵坐标最大/最小的线段编号
- mn:整个该节点维护区间内最大/最小纵坐标
其中t标记永久化,只有被取代时下传
实现见代码
Code
#include <cstdio> #include <cstdlib> #include <algorithm> #define ll long long using namespace std; const int N=4e5+10,M=4e5+10; const ll inf=123456789123456789; ll w,dis[N],edge[M],a,b; int n,m,u,v,head[N],ver[M],id,nxt[M],cnt,sum,rt,opt; int dep[N],dfn[N],son[N],si[N],re[N],top[N],fa[N],tot; struct line { ll k,b; }p[M]; struct node { int lc,rc,l,r,t; ll mn; }f[N>>1]; void addedge(int u,int v,ll w) { ver[++cnt]=v,nxt[cnt]=head[u],edge[cnt]=w,head[u]=cnt; } void dfs1(int u,int fu) { si[u]=1,fa[u]=fu,dep[u]=dep[fu]+1; for(int i=head[u],v;i;i=nxt[i]) { v=ver[i]; if(v==fu) continue; dis[v]=dis[u]+edge[i]; dfs1(v,u),si[u]+=si[v]; if(!son[u] || si[son[u]]<si[v]) son[u]=v; } } void dfs2(int u,int fu) { re[dfn[u]=++tot]=u; if(!son[u]) return; top[son[u]]=top[u],dfs2(son[u],u); for(int i=head[u],v;i;i=nxt[i]) { v=ver[i]; if(v==fu || v==son[u]) continue; top[v]=v,dfs2(v,u); } } inline ll calc(int x,int y) { return p[y].k*dis[re[x]]+p[y].b; } void build(int &g,int l,int r) { g=++sum; f[g].mn=inf,f[g].t=1; f[g].l=l,f[g].r=r; if(l==r) return; int mid=(l+r)>>1; build(f[g].lc,l,mid); build(f[g].rc,mid+1,r); } int lca(int x,int y) { int px=top[x],py=top[y]; while(px!=py) if(dep[px]>dep[py]) x=fa[px],px=top[x]; else y=fa[py],py=top[y]; return dep[x]<dep[y]?x:y; } void push_up(int g) { f[g].mn=min(f[g].mn,min(f[f[g].lc].mn,f[f[g].rc].mn)); } void add(int g,int l,int r,int x) { int gl=f[g].l,gr=f[g].r,mid=(gl+gr)>>1,t=f[g].t; if(l<=gl && r>=gr) { if(calc(gl,x)>=calc(gl,t) && calc(gr,x)>=calc(gr,t)) return; f[g].mn=min(f[g].mn,min(calc(gl,x),calc(gr,x))); if(calc(gl,x)<=calc(gl,t) && calc(gr,x)<=calc(gr,t)) { f[g].t=x; return ; } if(p[x].k<p[t].k) { if(calc(mid,x)<=calc(mid,t)) add(f[g].lc,l,r,f[g].t),f[g].t=x; else add(f[g].rc,l,r,x); } else { if(calc(mid,x)<=calc(mid,t)) add(f[g].rc,l,r,f[g].t),f[g].t=x; else add(f[g].lc,l,r,x); } push_up(g); return ; } if(r<=mid) add(f[g].lc,l,r,x); else if(l>mid) add(f[g].rc,l,r,x); else add(f[g].lc,l,mid,x),add(f[g].rc,mid+1,r,x); push_up(g); } void Add(int x,int y) { while(top[x]!=top[y]) add(rt,dfn[top[x]],dfn[x],id),x=fa[top[x]]; add(rt,dfn[y],dfn[x],id); } ll get(int g,int l,int r) { if(f[g].l>=l && f[g].r<=r) return f[g].mn; ll ans=inf; int mid=(f[g].l+f[g].r)>>1; if(p[f[g].t].b!=inf) ans=min(calc(max(l,f[g].l),f[g].t),calc(min(r,f[g].r),f[g].t)); if(r<=mid) return min(ans,get(f[g].lc,l,r)); else if(l>mid) return min(ans,get(f[g].rc,l,r)); else return min(ans,min(get(f[g].lc,l,mid),get(f[g].rc,mid+1,r))); } ll Get(int x,int y) { int px=top[x],py=top[y]; ll ans=inf; while(px!=py) if(dep[px]>dep[py]) ans=min(ans,get(rt,dfn[px],dfn[x])),x=fa[px],px=top[x]; else ans=min(ans,get(rt,dfn[py],dfn[y])),y=fa[py],py=top[y]; if(dfn[x]<dfn[y]) ans=min(ans,get(rt,dfn[x],dfn[y])); else ans=min(ans,get(rt,dfn[y],dfn[x])); return ans; } int main() { scanf("%d%d",&n,&m); for(int i=1;i<n;i++) { scanf("%d%d%lld",&u,&v,&w); addedge(u,v,w),addedge(v,u,w); } dfs1(1,0),top[1]=1,dfs2(1,0); p[++id]=(line){0,inf}; build(rt,1,n); while(m--) { scanf("%d%d%d",&opt,&u,&v); if(opt==1) { int la=lca(u,v); scanf("%lld%lld",&a,&b); p[++id]=(line){-a,a*dis[u]+b}; Add(u,la); p[++id]=(line){a,a*(dis[u]-(dis[la]<<1))+b}; Add(v,la); } else printf("%lld ",Get(u,v)); } return 0; }
以上是关于[SDOI2016]游戏(树链剖分,李超线段树模板)的主要内容,如果未能解决你的问题,请参考以下文章
bzoj 3531 [Sdoi2014]旅行(树链剖分,线段树)