瓜瓜的时空旅行

10 篇文章 0 订阅
4 篇文章 0 订阅

题目描述

西瓜们生活在编号 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;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值