【LuoGu】1351 联合权值

一花一世界,一叶一菩提 / 2023-09-02 / 原文

[NOIP2014 提高组] 联合权值

题目描述

无向连通图 \(G\)\(n\) 个点,\(n-1\) 条边。点从 \(1\)\(n\) 依次编号,编号为 \(i\) 的点的权值为 \(W_i\),每条边的长度均为 \(1\)。图上两点 \((u, v)\) 的距离定义为 \(u\) 点到 \(v\) 点的最短距离。对于图 \(G\) 上的点对 \((u, v)\),若它们的距离为 \(2\),则它们之间会产生 \(W_v \times W_u\) 的联合权值。

请问图 \(G\) 上所有可产生联合权值的有序点对中,联合权值最大的是多少?所有联合权值之和是多少?

输入格式

第一行包含 \(1\) 个整数 \(n\)

接下来 \(n-1\) 行,每行包含 \(2\) 个用空格隔开的正整数 \(u,v\),表示编号为 \(u\) 和编号为 \(v\) 的点之间有边相连。

最后 \(1\) 行,包含 \(n\) 个正整数,每两个正整数之间用一个空格隔开,其中第 \(i\) 个整数表示图 \(G\) 上编号为 \(i\) 的点的权值为 \(W_i\)

输出格式

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

样例 #1

样例输入 #1

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

样例输出 #1

20 74

提示

本例输入的图如上所示,距离为 \(2\) 的有序点对有\((1,3)\)\((2,4)\)\((3,1)\) 、$(3,5) \(、\)(4,2)$ 、$(5,3) $。

其联合权值分别为 \(2,15,2,20,15,20\)。其中最大的是 \(20\),总和为 \(74\)

【数据说明】

  • 对于 \(30\%\) 的数据,\(1 < n \leq 100\)
  • 对于 \(60\%\) 的数据,\(1 < n \leq 2000\)
  • 对于 \(100\%\) 的数据,\(1 < n \leq 2\times 10^5\)\(0 < W_i \leq 10000\)

保证一定存在可产生联合权值的有序点对。

解决方案

注意到题目所给图为一棵树\(n\)个点,\(n-1\)条边),因此图中一定无环。
因此对于距离为\(2\)的点就只有两种情况:
1、祖父-孙子结点
2、兄弟结点
注意到上述两种情况都存在一个中间结点,假定为\(u\)。1,2两种情况都可以归到\(u\)的邻接结点。
因此,我们可以枚举所有的点\(u\),然后遍历所有与\(u\)相邻的结点来得到我们想要的答案。
现在来看如何求解题目所需要的答案:
最大联合权值
对于一个中间结点\(u\),它的所有邻接结点能够产生的最大联合权值等于邻接结点中最大权值点乘以次大权值点。因此只需要在遍历过程中维护两个变量\(maxw1, maxw2\)来记录最大权值与次大权值即可。
联合权值和
假设\(u\)\(n\)个邻接点\(x_1, x_2, ..., x_n\)。联合权值之和\(sumw\)等于\(2\sum_{i=1}^n\sum_{j=1}^nx_ix_j\)。直接计算的话时间复杂度会到平方级别,会\(TLE\)。因此需要另寻他法(数学)
假设\(n=2\),我们可以发现:\((x_1 + x_2)^2=x_1^2+x_2^2+2x_1x_2\)。其中后面的\(2x_1x_2\)就是我们要求的。
推广到任意正整数\(n > 2\),有\((\sum_{i=1}^nx_i)^2=\sum_{i=1}^nx_i^2+2\sum_{i=1}^n\sum_{j=1}^n x_ix_j\).
因此对于一个中间结点产生的联合权值和等于\((\sum_{i=1}^nx_i)^2-\sum_{i=1}^nx_i^2\)。只需要在遍历过程中已经遍历过的结点的权值和即可。

#include<bits/stdc++.h>

const int N = 200010, MOD = 10007;

int n;
std::vector<std::vector<int>> g(N);
int w[N];
int answ, maxw;

int main(){
	std::ios::sync_with_stdio(false);
	std::cin.tie(nullptr);
	
	std::cin >> n;
	for(int i = 0; i < n - 1; i ++ ){
		int u, v;
		std::cin >> u >> v;
		g[u].push_back(v);
		g[v].push_back(u);
	}

	for(int i = 1; i <= n; i ++ ){
		std::cin >> w[i];
	}

	for(int i = 1; i <= n; i ++ ){
		int sumsq = 0, sqsum = 0;
		int maxw1 = 0, maxw2 = 0;
		for(auto v:g[i]){
			if(w[v] > maxw1){
				maxw2 = maxw1;
				maxw1 = w[v];
			}
			else if(w[v] > maxw2){
				maxw2 = w[v];
			}
			sumsq = (sumsq + w[v]) % MOD;
			sqsum = (sqsum + w[v] * w[v]) % MOD;
		}
		sumsq = sumsq * sumsq % MOD;
		answ = (answ + sumsq - sqsum + MOD) % MOD;
		maxw = std::max(maxw, maxw2 * maxw1);
	}

	std::cout << maxw << " " << answ;
	return 0;
}