[NOIP 2014] 联合权值
无向连通图 G 有 n 个点,n−1 条边。点从 1 到 n 依次编号,编号为 i 的点的权值为 Wi,每条边的长度均为 1。图上两点 (u,v) 的距离定义为 u 点到 v 点的最短距离。对于图 G 上的点对 (u,v),若它们的距离为 2,则它们之间会产生 Wu×Wv 的联合权值。
请问图 G 上所有可产生联合权值的有序点对中,联合权值最大的是多少?所有联合权值之和是多少?
输入格式
第一行包含 1 个整数 n。
接下来 n−1 行,每行包含 2 个用空格隔开的正整数 u、v,表示编号为 u 和编号为 v 的点之间有边相连。
最后 1 行,包含 n 个正整数,每两个正整数之间用一个空格隔开,其中第 i 个整数表示图 G 上编号为 i 的点的权值为 Wi。
输出格式
输出共 11 行,包含 22 个整数,之间用一个空格隔开,依次为图 G 上联合权值的最大值和所有联合权值之和。由于所有联合权值之和可能很大,输出它时要对 10007 取余。
数据范围
对于 30% 的数据,1<n≤100;
对于 60% 的数据,1<n≤2000;
对于 100% 的数据,1<n≤200,000,0<Wi≤10,000。
解析
这个题是求一棵树上,所有距离为 2 的点对的点权的乘积的和,以及所有距离为 2 的点对的点权的乘积的最大值。如果用暴力写,枚举中间点,那实际求的两个点一定是与这个中间点相连的,暴力枚举这两个点,会发现在一种特殊的情况下,所有点都和一个点相邻,复杂度就会达到平方,就会超时。
对于最大值,比较简单,在枚举中间点以后就可以直接取两个最大的相乘。
对于和,比较麻烦,可以这样优化,考虑如何快速计算以每个点为中间点的结果,会发现当枚举了其中一个点的时候,另一个点一定可以是中间点相连的除了已经枚举的那个点以外的所有点。所以可以在枚举中间点的基础上再枚举其中一个点,乘上其他与中间点相邻的点的权值和,这个权值和可以通过所有与中间点相邻的点的权值和减掉枚举的这个点的权值得到。
这样总复杂度降到了 O(n) 。
二、参考代码
#include <cstring>
#include <iostream>
#include <algorithm>
#include <vector>
using namespace std;
const int mod = 10007;
const int maxn = 200005;
vector < int > G[maxn];
void addedge(int u, int v) {
G[u].push_back(v);
}
int S[maxn];
int W[maxn];
int n, maxx, sum;
int main() {
cin >> n;
for (int i = 0; i < n - 1; i++) {
int u, v;
cin >> u >> v;
addedge(u, v);
addedge(v, u);
}
for (int i = 1; i <= n; i++) {
cin >> W[i];
}
for (int i = 1; i <= n; i++) {
int max1 = 0, max2 = 0;
max1 = 0;
max2 = 0;
for(int j = 0; j < G[i].size(); j++){
S[i] = (S[i] + W[G[i][j]]) % mod;
if (W[G[i][j]] >= max1){
max2 = max1;
max1 = W[G[i][j]];
} else if (W[G[i][j]] > max2){
max2 = W[G[i][j]];
}
}
maxx = max(maxx, max1 * max2);
}
for(int i = 1; i <= n ; i++){
for(int j = 0; j < G[i].size(); j++){
sum = (sum + (S[i] - W[G[i][j]] + mod)* W[G[i][j]]) % mod;
}
}
cout << maxx << " " << sum << endl;
return 0;
}