题解:CF838B Diverging Directions
给定一个
边权为
从
- 如果
u 能沿着树上的边走到v ,那肯定沿着树走最优。 - 否则
u 必须先回到1 ,再从1 沿着树走到v 。
维护
然后第
这里其实利用了差分的思想,将区间求和的问题转化为了前缀和相减。
考虑求出
从直觉上讲,很容易以为就是后
int Fa[22][N],n,dep[N],q;
int u[N<<1],v[N<<1],w[N<<1],len[N];
int End[N],rec[N],dfscnt,Trans[N];
ll Len[N];
//len[i] 表示从 i 到 1 的直接边的长度
//Len[i] 表示初始时 1 沿着树到 i 的总长度
struct Segment_Tree{
int ls[N<<1],rs[N<<1],rt,ndcnt;
ll sum[N<<1],tag[N<<1],minn[N<<1];
void pushup(int o){
sum[o]=sum[ls[o]]+sum[rs[o]];
minn[o]=min(minn[ls[o]],minn[rs[o]]);
}
void pushdown(int o,int l,int r){
tag[ls[o]]+=tag[o];
tag[rs[o]]+=tag[o];
minn[ls[o]]+=tag[o];
minn[rs[o]]+=tag[o];
int mid=(l+r)>>1;
sum[ls[o]]+=tag[o]*(mid-l+1);
sum[rs[o]]+=tag[o]*(r-mid);
tag[o]=0;
}
void build(int &o,int l,int r,int tpe){
o=++ndcnt;
tag[o]=sum[o]=minn[o]=0;
if (l==r){
if (tpe==1) sum[o]=minn[o]=Len[Trans[l]];
else sum[o]=minn[o]=Len[Trans[l]]+len[Trans[r]];
return;
}
int mid=(l+r)>>1;
build(ls[o],l,mid,tpe);
build(rs[o],mid+1,r,tpe);
return pushup(o);
}
void modify(int o,int l,int r,int p,int q,int v){
if (p<=l&&r<=q){
tag[o]+=v;
minn[o]+=v;
sum[o]+=1ll*v*(r-l+1);
return;
}
if (tag[o]) pushdown(o,l,r);
int mid=(l+r)>>1;
if (p<=mid) modify(ls[o],l,mid,p,q,v);
if (mid<q) modify(rs[o],mid+1,r,p,q,v);
return pushup(o);
}
ll query(int o,int l,int r,int p){
if (l==r) return sum[o];
if (tag[o]) pushdown(o,l,r);
int mid=(l+r)>>1;
if (p<=mid) return query(ls[o],l,mid,p);
else return query(rs[o],mid+1,r,p);
}
ll query(int o,int l,int r,int p,int q){
if (p<=l&&r<=q) return minn[o];
if (tag[o]) pushdown(o,l,r);
int mid=(l+r)>>1;ll ans=1e18;
if (p<=mid) ans=min(ans,query(ls[o],l,mid,p,q));
if (mid<q) ans=min(ans,query(rs[o],mid+1,r,p,q));
return ans;
}
}SGT,sgt;
struct edge{
int nxt,to,len;
}e[N<<1];int h[N],ecnt;
void add(int u,int v,int w){
e[++ecnt]=(edge){h[u],v,w};h[u]=ecnt;
}
void dfs(int u,int fa){
rec[u]=++dfscnt;
Trans[dfscnt]=u;
dep[u]=dep[fa]+1;
Fa[0][u]=fa;
for(int i=1;i<=20;i++)
Fa[i][u]=Fa[i-1][Fa[i-1][u]];
for(int i=h[u];i;i=e[i].nxt){
int v=e[i].to;
if (v==fa) continue;
Len[v]=Len[u]+e[i].len;
dfs(v,u);
}
End[u]=dfscnt;//记录 dfs 序
}
int LCA(int u,int v){
if (dep[u]<dep[v]) swap(u,v);
for(int i=20;i>=0;i--)
if (dep[Fa[i][u]]>=dep[v])
u=Fa[i][u];
if (u==v) return u;
for(int i=20;i>=0;i--)
if (Fa[i][u]!=Fa[i][v]){
u=Fa[i][u];
v=Fa[i][v];
}
return Fa[0][v];
}
ll query(int u){
return sgt.query(sgt.rt,1,n,rec[u],End[u])-SGT.query(SGT.rt,1,n,rec[u]);
}
ll query(int u,int v){
ll res;
if (u==v) return 0;
if (LCA(u,v)==u) res=SGT.query(SGT.rt,1,n,rec[v])-SGT.query(SGT.rt,1,n,rec[u]);
else res=query(u)+SGT.query(SGT.rt,1,n,rec[v]);
return res;
}
int main(int argv,char *argc[]){
n=read();q=read();
for(int i=1;i<n;i++){
u[i]=read();
v[i]=read();
w[i]=read();
add(u[i],v[i],w[i]);
add(v[i],u[i],w[i]);
}
for(int j=1;j<n;j++){
int i=j+(n-1);//真实边标号
u[i]=read();
v[i]=read();//v[i] == 1
w[i]=read();
len[u[i]]=w[i];
}
dfs(1,0);
SGT.build(SGT.rt,1,n,1);
sgt.build(sgt.rt,1,n,2);
for(int i=1;i<=q;i++){
int opt=read(),s=read(),t=read();
if (opt==1){
if (s<n){//修改树上的边
SGT.modify(SGT.rt,1,n,rec[v[s]],End[v[s]],t-w[s]);
sgt.modify(sgt.rt,1,n,rec[v[s]],End[v[s]],t-w[s]);
//v[s] 的子树内所有点到 1 的距离发生变化
w[s]=t;
}
else{
sgt.modify(sgt.rt,1,n,rec[u[s]],rec[u[s]],t-len[u[s]]);
len[u[s]]=t;
}
}
else printf("%lld\n",query(s,t));
}
return 0;
}