CF838B

一道不错的题。

首先观察这棵树,得到以下事实:

  • \(1\) 为树根。
  • 树上的边是由父亲连向儿子的有向边
  • \(1\) 以外的节点都有一条连向 \(1\)有向边
  • 边权为正。

可知任意两点间都存在最短路,且这条最短路是不难求的。假设要求从 \(u\)\(v\) 的最短路,设 \(dis1_u\)\(1\)\(u\) 的路径长度,\(dis2_u\)\(u\)\(1\) 的路径长度,分以下两种情况讨论:

  • \(u\)\(v\) 的祖先

显然,此时的最短路即为 \(u\)\(v\) 的路径长度。由于边权为正,因此多走其它的边一定不优。答案即为 \(dis1_v - dis1_u\)

  • \(u\) 不是 \(v\) 的祖先

由于树上的边都是由深度小的点连向深度大的点,因此 \(u\) 无法只通过树上的边到达 \(v\),需要先回到 \(1\) 再去 \(v\)。而 \(u\) 能通过树上的边到达的点即为以 \(u\) 为根的子树。设 \(z\) 为以 \(u\) 为根的子树内一点,我们需要最小化 \(u\)\(z\) 的距离加上 \(z\)\(1\) 的距离的和,用式子表示为 \(\min{dis1_z-dis1_u+dis2_z}\)\(dis1_u\) 在最后处理即可,于是变为 \(\min{dis1_z+dis2_z}\),转化成了子树求最小值的问题,使用 dfs 序和线段树解决即可。

接下来讨论如何处理修改操作。对于树上的边,我们观察发现会导致以边的终点为根的子树的 \(dis1\) 发生改变,直接修改增量即可。如果不是树上的边,对该边的起点做单点修改即可。

注意单点查询 \(dis1\) 时,由于维护的是 \(dis1+dis2\),因此需要减去对应的 \(dis2\)(当然用两棵线段树分开维护也可以)。

时间复杂度 \(O(m\log n)\)

#include <iostream>
#include <cstdio>
#define int long long
#define N 300001
#define MAXN 30000000000000
 
using namespace std;
 
int n,T,Lx[N],Rx[N],dis1[N],dis2[N],hd[N],tot,cnt,fa[N][19],dep[N],rev[N];
 
struct E
{
	int u,v,w,nxt;
}e[N * 2];
 
struct T
{
	int lzy[N * 4],mi[N * 4],v[N * 4];
	void pushup( int u )
	{
		mi[u] = min( mi[u << 1] , mi[u << 1 | 1] );
		return;
	}
	void pushdown( int u )
	{
		if( lzy[u] )
		{
			mi[u << 1] += lzy[u];
			lzy[u << 1] += lzy[u];
			mi[u << 1 | 1] += lzy[u];
			lzy[u << 1 | 1] += lzy[u];
			lzy[u] = 0;
		}
		return;
	}
	void build( int u , int l , int r )
	{
		if( l == r )
		{
			v[u] = mi[u] = dis1[rev[l]] + dis2[rev[l]];
			return;
		}
		int mid = ( l + r ) >> 1;
		build( u << 1 , l , mid );
		build( u << 1 | 1 , mid + 1 , r );
		pushup( u );
	}
	void update( int u , int l , int r , int L , int R , int x )
	{
		if( L <= l && r <= R )
		{
			mi[u] += x;
			lzy[u] += x;
			return;
		}
		pushdown( u );
		int mid = ( l + r ) >> 1;
		if( L <= mid ) update( u << 1 , l , mid , L , R , x );
		if( R > mid ) update( u << 1 | 1 , mid + 1 , r , L , R , x );
		pushup( u );
		return;
	}
	int query1( int u , int l , int r , int L , int R )
	{
		if( L <= l && r <= R ) return mi[u];
		pushdown( u );
		int mid = ( l + r ) >> 1,res = MAXN;
		if( L <= mid ) res = min( res , query1( u << 1 , l , mid , L , R ) );
		if( R > mid ) res = min( res , query1( u << 1 | 1 , mid + 1 , r , L , R ) );
		return res;
	}
	int query2( int u , int l , int r , int x )
	{
		if( l == r ) return mi[u] - dis2[rev[l]];
		pushdown( u );
		int mid = ( l + r ) >> 1;
		if( x <= mid ) return query2( u << 1 , l , mid , x );
		else return query2( u << 1 | 1 , mid + 1 , r , x );
	}
}t;
 
void add( int u , int v , int w )
{
	tot ++;
	e[tot].u = u;
	e[tot].v = v;
	e[tot].w = w;
	e[tot].nxt = hd[u];
	hd[u] = tot;
}
 
void dfs1( int u , int ff )
{
	dep[u] = dep[ff] + 1;
	Lx[u] = ++ cnt;
	rev[cnt] = u;
	for( int i = hd[u] ; i ; i = e[i].nxt )
	{
		int v = e[i].v;
		if( Lx[v] ) continue;
		dis1[v] = dis1[u] + e[i].w;
		dfs1( v , u );
	}
	Rx[u] = cnt;
}
 
signed main()
{
	ios::sync_with_stdio( false );
	cin.tie( 0 );
	cout.tie( 0 );
	int op,u,v,w;
	cin >> n >> T;
	for( int i = 1 ; i < n ; i ++ )
	{
		cin >> u >> v >> w;
		add( u , v , w );
	}
	dfs1( 1 , 0 );
	for( int i = 1 ; i < n ; i ++ )
	{
		cin >> u >> v >> w;
		add( u , v , w );
		dis2[u] = w;
	}
	t.build( 1 , 1 , n );
	while( T -- )
	{
		cin >> op >> u >> v;
		if( op == 1 )
		{
			if( u < n ) t.update( 1 , 1 , n , Lx[e[u].v] , Rx[e[u].v] , v - e[u].w );
			else t.update( 1 , 1 , n , Lx[e[u].u] , Lx[e[u].u] , v - e[u].w ),dis2[e[u].u] = v;
			e[u].w = v;
		}
		if( op == 2 )
		{
			if( Lx[u] <= Lx[v] && Rx[v] <= Rx[u] ) cout << t.query2( 1 , 1 , n , Lx[v] ) - t.query2( 1 , 1 , n , Lx[u] ) << '\n';
			else cout << t.query1( 1 , 1 , n , Lx[u] , Rx[u] ) - t.query2( 1 , 1 , n , Lx[u] ) + t.query2( 1 , 1 , n , Lx[v] ) << '\n';
		}
	}
	return 0;
}
posted @ 2025-09-08 18:32  FormulaOne  阅读(18)  评论(0)    收藏  举报