联合权值(树上小操作)

题目描述

无向连通图 G G G n n n 个点, n − 1 n-1 n1 条边。点从 1 1 1 n n n 依次编号,编号为 i i i 的点的权值为 W i W_i Wi, 每条边的长度均为 1 1 1。图上两点 ( u , v ) (u, v) (u,v)的距离定义为 u u u 点到 v v v 点的最短距离。对于图 G G G 上的点对 ( u , v ) (u, v) (u,v),若它们的距离为 2 2 2,则它们之间会产生 W u W_u Wu × × × W v W_v Wv的联合权值。
请问图 G 上所有可产生联合权值的有序点对中,联合权值最大的是多少?所有联合权值之和是多少?

输入格式

第一行包含 1 1 1个整数 n n n
接下来 n − 1 n-1 n1 行,每行包含 2 2 2 个用空格隔开的正整数 u 、 v u、v uv,表示编号为 u u u 和编号为 v v v 的点 之间有边相连。
最后 1 1 1 行,包含 n n n 个正整数,每两个正整数之间用一个空格隔开,其中第 i i i 个整数表示 图 G G G 上编号为 i i i 的点的权值为 W i W_i Wi

输出格式

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

样例输入

5 5 5
1 1 1 2 2 2
2 2 2 3 3 3
3 3 3 4 4 4
4 4 4 5 5 5
1 1 1 5 5 5 2 2 2 3 3 3 10 10 10

样例输出

20 20 20 74 74 74

限制

对于 30 30 30%的数据, 1 &lt; n ≤ 100 1 &lt; n ≤ 100 1<n100
对于 60 60 60%的数据, 1 &lt; n ≤ 2000 1 &lt; n ≤ 2000 1<n2000
对于 100 100 100%的数据, 1 &lt; n ≤ 200 , 000 , 0 &lt; W i ≤ 10 , 000 1 &lt; n ≤ 200,000,0 &lt; W_i ≤ 10,000 1<n200,0000<Wi10,000

解析

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

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

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

代码

#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;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值