0%

[NOIp2014] 联合权值

题目

题目链接

题目大意:给出一棵 $n$ 个点的树,若树上两点 $u, v$ 满足距离为 $2$,则产生 $w_u \times w_v$ 的联合权值。求出树上最大的联合权值和所有联合权值的和。权值之和对 $10007$ 取余。$1 \le n \le 200000, 0 < w_i \le 10000$。

思路

(以前的暴力分好多啊)

考虑枚举离这两个点距离为 $1$ 的中间点,则只需要对每个点找出边连的点进行更新就行了。

最大值是很好求的,现在要求以这个点为中间点的所有权值之和。

不难发现 $(\sum w_i)^2 - \sum w_i^2$ 就是我们要求的答案。

注意这道题只对和取模,最大值是不取模的。

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <vector>

template <class T>
inline void read(T &x) {
x = 0;
int f = 0;
char ch = getchar();
while (!isdigit(ch)) { f |= ch == '-'; ch = getchar(); }
while (isdigit(ch)) { x = (x << 1) + (x << 3) + (ch ^ 48); ch = getchar(); }
x = f ? -x : x;
return ;
}

typedef long long LL;
typedef unsigned long long uLL;

const LL mod = 10007;

std::vector<int> g[200010];
LL w[200010];
LL ans, max;
int n;

int main() {
read(n);
for (int i = 1, u, v; i < n; ++i) {
read(u), read(v);
g[u].push_back(v), g[v].push_back(u);
}
for (int i = 1; i <= n; ++i) read(w[i]);
for (int i = 1; i <= n; ++i) {
LL mx1 = 0, mx2 = 0, s = 0, sum = 0;
for (auto j : g[i]) {
if (w[j] > mx1) mx2 = mx1, mx1 = w[j];
else if (w[j] > mx2) mx2 = w[j];
}
max = std::max(max, mx1 * mx2);
for (auto j : g[i]) {
s += w[j];
s %= mod;
sum += (w[j] % mod) * (w[j] % mod);
sum %= mod;
}
ans += (s % mod) * (s % mod) - sum % mod;
ans %= mod;
}
printf("%lld %lld\n", max, ans);
}