先树链剖分转成区间问题。
S到T的路径可以拆成S-lca和lca-T。
设d[r]表示根到r的距离。
对于S-lca中的点r,加的值为:
a∗(d[s]−d[r])+b=(−a)∗d[r]+(a∗d[s]+b)
对于lca-T中的点r,加的值为:
a∗(d[r]+d[s]−2d[lca])+b=a∗d[r]+(a∗d[s]+b−2a∗d[lca])
这样就可以转成将一个区间的每个点r的值与一次函数在d[r]处的值取min。
在线段树上维护这个东西。
线段树上每个点维护一条跨过当前点的线段。
修改一段区间时对于线段树上受影响的点,如果在当前区间,增加的线段与当前线段没有交点那么将当前线段修改为在上面的那条。
否则求交点,然后往下递归传一条线段。
注意由于区间查询所以需要pushdown
区间查询时每到一个区间用当前区间的直线更新答案,然后正常往下查询。
#include <bits/stdc++.h>
using namespace std;
#define N 110000
#define ll long long
#define ls l,mid,now<<1
#define rs mid+1,r,now<<1|1
#define inf 123456789123456789ll
int n,m,tot,cnt;
int head[N],nex[N<<1],to[N<<1],val[N<<1];
int deep[N],fa[N],son[N],top[N],pos[N],bel[N],size[N];
ll d[N],v[N<<3];
struct node
{
ll a,b;
node(){}
node(ll a,ll b):a(a),b(b){}
ll cal(ll x){return a*x+b;}
}tr[N<<3],v1;
double ins(node r1,node r2)
{return (double)(r2.b-r1.b)/(r1.a-r2.a);}
void add(int x,int y,int z)
{
tot++;
nex[tot]=head[x];head[x]=tot;
to[tot]=y;val[tot]=z;
}
void dfs1(int x,int y)
{
size[x]=1;fa[x]=y;
deep[x]=deep[y]+1;
for(int i=head[x];i;i=nex[i])
if(to[i]!=y)
{
d[to[i]]=d[x]+val[i];
dfs1(to[i],x);
size[x]+=size[to[i]];
son[x]=size[to[i]]>size[son[x]] ? to[i]:son[x];
}
}
void dfs2(int x,int y,int tp)
{
top[x]=tp;
pos[x]=++cnt;bel[cnt]=x;
if(son[x])dfs2(son[x],x,tp);
for(int i=head[x];i;i=nex[i])
if(to[i]!=y&&to[i]!=son[x])
dfs2(to[i],x,to[i]);
}
void build(int l,int r,int now)
{
tr[now]=node(0,inf);
v[now]=inf;
if(l==r)return;
int mid=(l+r)>>1;
build(ls);build(rs);
}
int lca(int x,int y)
{
while(top[x]!=top[y])
{
if(deep[top[x]]<deep[top[y]])swap(x,y);
x=fa[top[x]];
}
return deep[x]<deep[y] ? x:y;
}
void pushup(int x)
{
v[x]=min(v[x],v[x<<1]);
v[x]=min(v[x],v[x<<1|1]);
}
void insert(int l,int r,int now,node v1)
{
ll lv=v1.cal(d[bel[l]]),rv=v1.cal(d[bel[r]]);
ll lp=tr[now].cal(d[bel[l]]),rp=tr[now].cal(d[bel[r]]);
if(lv>=lp&&rv>=rp)return;
if(lv<=lp&&rv<=rp)tr[now]=v1;
else
{
int mid=(l+r)>>1;
double x=ins(v1,tr[now]),t=(double)(d[bel[mid]]+d[bel[mid+1]])/2.0;
if(lv>lp)
{
if(x>=t)insert(rs,v1);
else insert(ls,tr[now]),tr[now]=v1;
}
else
{
if(x<=t)insert(ls,v1);
else insert(rs,tr[now]),tr[now]=v1;
}
}
v[now]=min(v[now],tr[now].cal(d[bel[l]]));
v[now]=min(v[now],tr[now].cal(d[bel[r]]));
pushup(now);
}
void update(int l,int r,int now,int lq,int rq)
{
if(lq<=l&&r<=rq)
{insert(l,r,now,v1);return;}
int mid=(l+r)>>1;
if(mid>=lq)update(ls,lq,rq);
if(mid<rq) update(rs,lq,rq);
pushup(now);
}
void update(int x,int y)
{
while(top[x]!=top[y])
{
update(1,n,1,pos[top[x]],pos[x]);
x=fa[top[x]];
}
update(1,n,1,pos[y],pos[x]);
}
ll get(int l,int r,int now,int lq,int rq)
{
l=max(l,lq);r=min(r,rq);
return min(tr[now].cal(d[bel[l]]),tr[now].cal(d[bel[r]]));
}
ll query(int l,int r,int now,int lq,int rq)
{
if(lq<=l&&r<=rq)return v[now];
int mid=(l+r)>>1;
ll ret=get(l,r,now,lq,rq);
if(mid>=lq)ret=min(ret,query(ls,lq,rq));
if(mid<rq) ret=min(ret,query(rs,lq,rq));
return ret;
}
ll query(int x,int y)
{
ll ret=inf;
while(top[x]!=top[y])
{
if(deep[top[x]]<deep[top[y]])swap(x,y);
ret=min(ret,query(1,n,1,pos[top[x]],pos[x]));
x=fa[top[x]];
}
if(deep[x]<deep[y])swap(x,y);
ret=min(ret,query(1,n,1,pos[y],pos[x]));
return ret;
}
int main()
{
memset(v,127,sizeof(v));
scanf("%d%d",&n,&m);
for(int i=1,x,y,v;i<n;i++)
{
scanf("%d%d%d",&x,&y,&v);
add(x,y,v);add(y,x,v);
}
dfs1(1,0);
dfs2(1,0,1);
build(1,n,1);
for(int tp,s,t,a,b;m--;)
{
scanf("%d",&tp);
if(tp==1)
{
scanf("%d%d%d%d",&s,&t,&a,&b);
int lc=lca(s,t);
v1=node(-a,a*d[s]+b);update(s,lc);
v1=node(a,a*d[s]+b-2*a*d[lc]);update(t,lc);
}
else
{
scanf("%d%d",&s,&t);
printf("%lld\n",query(s,t));
}
}
return 0;
}
本文介绍了一种利用树链剖分将路径操作问题转化为区间问题的方法,并通过线段树维护区间上的最值,实现了对路径上每个节点进行高效更新与查询的功能。
1980

被折叠的 条评论
为什么被折叠?



