联合权值(树上小操作)

题目描述

无向连通图 GGGnnn 个点,n−1n-1n1 条边。点从 111nnn 依次编号,编号为 iii 的点的权值为 WiW_iWi, 每条边的长度均为 111。图上两点(u,v)(u, v)(u,v)的距离定义为 uuu 点到 vvv 点的最短距离。对于图 GGG 上的点对(u,v)(u, v)(u,v),若它们的距离为 222,则它们之间会产生WuW_uWu×××WvW_vWv的联合权值。
请问图 G 上所有可产生联合权值的有序点对中,联合权值最大的是多少?所有联合权值之和是多少?

输入格式

第一行包含111个整数 nnn
接下来 n−1n-1n1 行,每行包含 222 个用空格隔开的正整数 u、vu、vuv,表示编号为 uuu 和编号为 vvv 的点 之间有边相连。
最后 111 行,包含 nnn 个正整数,每两个正整数之间用一个空格隔开,其中第 iii 个整数表示 图 GGG 上编号为 iii 的点的权值为WiW_iWi

输出格式

输出共 111 行,包含 222 个整数,之间用一个空格隔开,依次为图 GGG 上联合权值的最大值 和所有联合权值之和。由于所有联合权值之和可能很大,输出它时要对 100071000710007 取余。

样例输入

555
111 222
222 333
333 444
444 555
111 555 222 333 101010

样例输出

202020 747474

限制

对于 303030%的数据,1&lt;n≤1001 &lt; n ≤ 1001<n100
对于 606060%的数据,1&lt;n≤20001 &lt; n ≤ 20001<n2000
对于 100100100%的数据,1&lt;n≤200,000,0&lt;Wi≤10,0001 &lt; n ≤ 200,000,0 &lt; W_i ≤ 10,0001<n200,0000<Wi10,000

解析

原图就是一棵无根树啊。
树上u,vu,vu,v两点间距离为222只有两种情况:

  • u,vu,vu,v之间是祖父与孙子的关系
  • u,vu,vu,v之间是兄弟的关系

因为一个节点至多有一个祖父节点,而有可能有多个孙子节点,所以我们可以找每个节点的祖父节点,并将和的答案乘222即可。
对于第二种情况我们只要统计每个节点权值最大和次大的儿子节点即可求出最大值。
S=∑v∈son[u]w[v]{S=\sum_{v∈son[u]} w[v]}S=vson[u]w[v]
T=∑v∈son[u]w[v]2{T=\sum_{v∈son[u]} w[v]^2}T=vson[u]w[v]2
至于和,对于节点uuu,它的儿子的权值和即为
S2−TS^2-TS2T
轻松解决此题。

代码

#include<cstdio>
using namespace std;
const int p = 10007;
const int maxn = 200005;
const int maxe = 200005;
int edgenum;
int Next[maxe << 1] , vet[maxe << 1] , head[maxn];
int fa[maxn] , mx[maxn] , cmx[maxn] , sum[maxn] , sqr_sum[maxn];
int w[maxn];
int max(int x , int y){return x > y ? x : y;}
int sqr(int x){return x * x;}
int read()
{
	char ch = getchar();
	while(ch < '0' || ch > '9') ch = getchar();
	int res = 0;
	while(ch >= '0' && ch <= '9') res = (res << 3) + (res << 1) + (ch ^ 48) , ch = getchar();
	return res;
}
void clear_edge(int n)
{
	edgenum = 0;
	for(int i = 1;i <= n;i++) head[i] = 0;
}
void add_edge(int u , int v)
{
	edgenum++;
	Next[edgenum] = head[u];
	vet[edgenum] = v;
	head[u] = edgenum;
}
void dfs(int u , int father)
{
	fa[u] = father;
	mx[u] = 0;
	cmx[u] = 0;
	sum[u] = 0;
	sqr_sum[u] = 0;
	for(int e = head[u];e;e = Next[e])
	{
		int v = vet[e];
		if(v == fa[u]) continue;
		dfs(v , u);
		if(w[v] > mx[u]) cmx[u] = mx[u] , mx[u] = w[v];
		else if(w[v] > cmx[u]) cmx[u] = w[v];
		sum[u] = (sum[u] + w[v]) % p;
		sqr_sum[u] = (sqr_sum[u] + sqr(w[v]) % p) % p;
	}
}
int main()
{
	freopen("link.in","r",stdin);
	freopen("link.out","w",stdout);
	int n = read();
	clear_edge(n);
	for(int i = 1;i < n;i++)
	{
		int u = read() , v = read();
		add_edge(u , v) , add_edge(v , u);
	}
	for(int i = 1;i <= n;i++) w[i] = read();
	dfs(1 , 0);
	int ans_max = 0 , ans_tot = 0;
	for(int i = 1;i <= n;i++)
		if(fa[fa[i]] != 0)
		{
			ans_max = max(ans_max , w[i] * w[fa[fa[i]]]);
			ans_tot = (ans_tot + w[i] * w[fa[fa[i]]] % p) % p;
		}
	ans_tot = (ans_tot << 1) % p;
	for(int i = 1;i <= n;i++)
	{
		ans_max = max(ans_max , mx[i] * cmx[i]);
		ans_tot = (ans_tot + sqr(sum[i]) % p - sqr_sum[i] + p) % p;
	}
	printf("%d %d\n",ans_max,ans_tot);
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值