【题解】洛谷 P9584


题目

原题链接

题目

在这里插入图片描述

输入/输出例子

输入

9 5
2 3 6
6 1 4
5 2 10
2 4 1
9 1 9
2 8 3
1 2 3
7 4 8
4 9
7 3
6 1
9 7
2 1

输出

1050
1054
970
1148
896

提示


解题思路

思路

树形DP。

分析

我们先把前 n n n 个点两两之间距离求出来。这一步可以转换为求路径,及每条边经过的次数 × \times × 边权的和。

对于第 n + 1 n+1 n+1 个点,我们发现其和前 n n n 个点的距离之和可以转换为两个东西:剩下 n − 1 n-1 n1 个点到连接点的距离之和和新连接的边的经过次数。

定义 f [ i ] f[i] f[i] 表示原来的树中剩下 n − 1 n-1 n1 个节点到 i i i 号节点的距离之和。

那么在第一次 dfs 中, f [ 1 ] f[1] f[1] 是可以算出来的。

在第二次 dfs 中而前 n n n 个点两两之间距离之和也可以求出来。

对于剩下的 f [ i ] f[i] f[i],我们发现,对于一个子节点 v v v,若它的父亲节点是 u u u,那么可以得出 f [ v ] = f [ u ] + w × ( n − 2 × s z [ v ] ) f[v]=f[u]+w\times(n-2\times sz[v]) f[v]=f[u]+w×(n2×sz[v]),其中 w w w 表示连接点 u u u 和点 v v v 的边的边权, s z [ v ] sz[v] sz[v] 表示以 v v v 为根节点的子树有几个节点。

算出所有的 f [ i ] f[i] f[i] 后,对于每次的询问,可以 O ( 1 ) O(1) O(1) 得出答案。

Code


#include<bits/stdc++.h>
#define int long long
#define mod 998244353
using namespace std;
const int N=1e6+1;
int n,m,head[N],sz[N],x,y,z,idx,ans,d[N],up[N],f[N],g[N];
struct fy
{
	int v,w,next;
}edge[N<<1];
void add(int x,int y,int z)
{
	edge[++idx].v=y,edge[idx].w=z,edge[idx].next=head[x],head[x]=idx;
}
void dfs(int u,int fa)
{
	sz[u]=1;
	for(int i=head[u];i;i=edge[i].next)
	{
		int v=edge[i].v,w=edge[i].w;
		if(v==fa)
			continue;
		dfs(v,u);
		sz[u]+=sz[v];
		sz[u]%=mod;
		d[u]+=w*sz[v];
		d[u]+=d[v];
		d[u]%=mod;
	}
}
void dfs_(int u,int fa)
{
	for(int i=head[u];i;i=edge[i].next)
	{
		int v=edge[i].v,w=edge[i].w;
		if(v==fa)
			continue;
		dfs_(v,u);
		ans+=(sz[v]*(n-sz[v]))*w;
		ans%=mod;
	}
}
void dfs__(int u,int fa)
{
	for(int i=head[u];i;i=edge[i].next)
	{
		int v=edge[i].v,w=edge[i].w;
		if(v==fa)
			continue;
		f[v]=f[u]+w*(n-2*sz[v]);
		f[v]%=mod;
		f[v]=(f[v]+mod)%mod;//注意一定要加上mod
		dfs__(v,u);
	}
}
signed main()
{
	scanf("%lld%lld",&n,&m);
	for(int i=1;i<n;i++)
	{
		scanf("%lld%lld%lld",&x,&y,&z);
		add(x,y,z);
		add(y,x,z);
	}
	dfs(1,0);
	dfs_(1,0);
	f[1]=d[1];
	dfs__(1,0);
   //初始化f[i]
	for(int i=1;i<=m;i++)
	{
		scanf("%lld%lld",&x,&y);
		printf("%lld\n",(ans+f[x]+n*y%mod)*2%mod);
      //要乘2因为我们算的只是有序数对。
	}
}

更多方法

更多方法

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值