题目描述
西瓜们生活在编号 1···n 的 n个平行时空中,2n−2 台时光机将这些平行时空联系在一起。一台时光机有 3个整数参数 u,v,t 表示从时空 u 可以花费 t 的时间穿梭到时空 v。
为了确保时空之间可以相互穿梭,同时方便作为现世的 1号时空的通行,西瓜们将这些时光机进行分工:前 n−1 台
时光机确保从 1号时空可以直接 / 间接抵达任意时空,后 n−1台时光机负责从 2···n号时空直接返回 1号时空。
q1个西瓜希望从一些时空穿梭到一些时空。显然,智慧的西瓜们会选择最省时的路线。然而,时光机并不稳定,
在此期间,时光机的运行时间会发生q2次变化。西瓜王被吃了,希望你告诉他的子民西瓜们当前最优路线所需的
时间。
正解
DFS序+线段树其中dfs序是为了把区间不连贯的树整理成一段连续的区间,为线段树的使用提供方便。线段树用来维护区间最小值。
对于dfs序,我们给每个点第一次进入dfs时特别标记一次,给其专门一个编号;离开dfs时公用当时的编号即可,因为这个编号足以包含它的所有子树的节点。
我们定义线段树为求从ql到qr内一节点出发,直接到达1后再回到该节点的最短路径。线段树常规求区间最小值。因为涉及到路径修改,对于一条从1出发的路径(起始点x,终止点y)修改,这一改变会影响到y子树内的所有节点,要进行(L[y],R[y])区间的修改。对于回到1的路径(起始点x),只对x回到1有影响(因为定义里写的是直接),故只需修改(L[x],L[x])区间。
询问的话要分两种情况来考虑。
一是y是x子树内的一节点,那么直接由x到y为最短路径。此时会有 L[x]<L[y]<R[x] 的特征,答案为dis(1->y->1)-dis(y->1)-( dis(1->x->1)+dis(x->1) )。
二是x需要先到x子树内一节点,然后向上到1,再到y。这样的答案为dis(1->x'->1)-( dis(1->x->1)-dis(x->1) )+( dis(1->y->1)-dis(y->1) ),其中dis(1->x'->1)-( dis(1->x->1)-dis(x->1) )能求出x回到1的最短距离。
总结
本题关键:一是一个判断和求一个距离,判断是否在其一在另一子树内,然后求出两点间直接到达的距离;二是求一个最短和一个距离,从x回1的最短路径,从1到y的距离。然后利用线段树可以快速求到区间最小值,巧妙地设计了记录每个节点一来一回的距离的线段树,包含了完整的情况。这样再利用加加减减就可以表达出各段路径的距离了。
代码
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
typedef long long ll;
const int maxn=2e5+10;
inline int read()
{
int re=0;char ch;
while(ch<'0'||ch>'9') ch=getchar();
while(ch>='0'&&ch<='9') re=re*10+(ch^48),ch=getchar();
return re;
}
int n,Q;
int L[maxn],R[maxn];
//***********邻接表**************
struct edge
{
int x,y,c,next;
}e[maxn*2];int last[maxn],len=0;
int to[maxn];
void ins(int x,int y,int c)
{
len++;
e[len].x=x;e[len].y=y;e[len].c=c;
e[len].next=last[x];last[x]=len;
}
void ins2(int x,int y,int c)
{
len++;
e[len].x=x;e[len].y=y;
}
int id=0,yss[maxn];
ll g[maxn];
void dfs(int x)
{
L[x]=++id;yss[id]=x;
for(int k=last[x];k;k=e[k].next)
{
int y=e[k].y;
g[y]=g[x]+e[k].c;
dfs(y);
}
R[x]=id;
}
//***********线段树****************
ll mn[maxn*2],lazy[maxn*2];//线段树辅助询问从1到x再由x到1的最小路径
void bt(int x,int l,int r)
{
if(l==r)
{
mn[x]=g[yss[l]];
return ;
}
int mid=l+r>>1;
int lc=x<<1,rc=lc|1;
bt(lc,l,mid);
bt(rc,mid+1,r);
mn[x]=min(mn[lc],mn[rc]);lazy[x]=0;
}
void pushdown(int x)
{
int lc=x<<1,rc=lc|1;
mn[lc]+=lazy[x];lazy[lc]+=lazy[x];
mn[rc]+=lazy[x];lazy[rc]+=lazy[x];
lazy[x]=0;
}
void add(int x,int l,int r,int ql,int qr,int k)
{
if(l==ql&&r==qr)
{
mn[x]+=k;
lazy[x]+=k;
return ;
}
if(lazy[x]!=0) pushdown(x);
int mid=l+r>>1;
int lc=x<<1,rc=lc|1;
if(qr<=mid) add(lc,l,mid,ql,qr,k);
else if(mid+1<=ql) add(rc,mid+1,r,ql,qr,k);
else add(lc,l,mid,ql,mid,k),add(rc,mid+1,r,mid+1,qr,k);
mn[x]=min(mn[lc],mn[rc]);
}
ll ask(int x,int l,int r,int ql,int qr)
{
if(l==ql&&r==qr)
{
return mn[x];
}
if(lazy[x]!=0) pushdown(x);
int mid=l+r>>1;
int lc=x<<1,rc=lc|1;
if(qr<=mid) return ask(lc,l,mid,ql,qr);
else if(mid+1<=ql) return ask(rc,mid+1,r,ql,qr);
else return min(ask(lc,l,mid,ql,mid),ask(rc,mid+1,r,mid+1,qr));
}
//*********************************
int main()
{
n=read();Q=read()+read();
for(int i=1;i<n;i++)
{
int x,y,c;
x=read();y=read();c=read();
ins(x,y,c);
}
dfs(1);
bt(1,1,n);
for(int i=1;i<n;i++)
{
int x,y,c;
x=read();y=read();c=read();
ins2(x,y,c);to[x]=c;
add(1,1,n,L[x],L[x],c);
}
while(Q--)
{
int id,x,y;
id=read();x=read();y=read();
if(id==1)
{
if(L[x]<=L[y]&&L[y]<=R[x])//x->y
{
ll ans=ask(1,1,n,L[y],L[y])-to[y]-(ask(1,1,n,L[x],L[x])-to[x]);
printf("%lld\n",ans);
}
else//x->(x子树内一子节点->)1->y
{
ll ans=(ask(1,1,n,L[x],R[x])-ask(1,1,n,L[x],L[x])+to[x])+(ask(1,1,n,L[y],L[y])-to[y]);
printf("%lld\n",ans);
}
}
else
{
if(x<n)
{
int u=e[x].y;//应更新到靠下的节点
add(1,1,n,L[u],R[u],y-e[x].c);//u之下的子树(L[u],R[u])全部增加y-e[x].c的距离
e[x].c=y;
}
else
{
int u=e[x].x;
add(1,1,n,L[u],L[u],y-to[u]);//u回到根的路径增加y-to[u]
to[u]=y;
}
}
}
return 0;
}