星座图 / 联合权值
题目链接:SSL 2373 / luogu P1351
题目大意
给你一个树,一对点满足条件,要它们的距离为 2。(边的距离为 1)
然后有点权,求满足的点对中,每对点两个点点权的积的和以及最大值。
和要对 10007 取模。
思路
你看到这个距离为 2 2 2 的很特别,那你考虑怎样就会距离为 2 2 2。
那你会发现,从一个点可以连向到若干个点,这若干个点两两之间距离都是
2
2
2。
那最大值就很好求了,我们直接枚举每个点,求与它相邻的点中点权最大的那两个,然后乘起来。
然后每个点都这么求一遍,取个最大值就是答案。
(最大值不用取模)
接着就是求和。
那你求每个点相邻的那一堆,然后再把每个加起来。
那每一堆怎么求呢?
两两配对都可以,但是我们枚举两个点就会超时。
那我们考虑乘法分配律一下,对于每个点,它都可以和别的点配对。
设点权和是
s
u
m
sum
sum,这个点点权是
x
x
x,这个点和别的配对的贡献就是
x
(
s
u
m
−
x
)
x(sum-x)
x(sum−x)。
(
x
x
1
+
x
x
2
+
.
.
.
x
x
n
=
x
(
x
1
+
x
2
+
.
.
.
+
x
n
)
=
x
(
s
u
m
−
x
)
xx_1+xx_2+...xx_n=x(x_1+x_2+...+x_n)=x(sum-x)
xx1+xx2+...xxn=x(x1+x2+...+xn)=x(sum−x),这里的
x
i
x_i
xi 是不包含
x
x
x 的其它点,所以和是
s
u
m
−
x
sum-x
sum−x)
然后因为 a , b a,b a,b 的组合和 b , a b,a b,a 的组合是不同的,所以我们并不用除二。
代码
#include<cstdio>
#include<iostream>
#define mo 10007
#define ll long long
using namespace std;
struct node {
int to, nxt;
}e[600001];
int n, le[200001], KK;
int x, y;
ll ans, maxn, qz[200001];
void add(int x, int y) {
e[++KK] = (node){y, le[x]}; le[x] = KK;
e[++KK] = (node){x, le[y]}; le[y] = KK;
}
int main() {
// freopen("link.in", "r", stdin);
// freopen("link.out", "w", stdout);
scanf("%d", &n);
for (int i = 1; i < n; i++) {
scanf("%d %d", &x, &y);
add(x, y);
}
for (int i = 1; i <= n; i++) scanf("%lld", &qz[i]);
for (int i = 1; i <= n; i++) {
ll first = 0, second = 0, sum = 0;
for (int j = le[i]; j; j = e[j].nxt) {
if (qz[e[j].to] > first) {//维护最大和第二大
second = first;
first = qz[e[j].to];
}
else if (qz[e[j].to] > second) {
second = qz[e[j].to];
}
sum = (sum + qz[e[j].to]) % mo;
}
maxn = max(maxn, first * second);
for (int j = le[i]; j; j = e[j].nxt) {//用每个点乘剩余点的和,每个点都这么乘一次,就可以搞出两两乘的和结果
sum = ((sum - qz[e[j].to]) % mo + mo) % mo;
ans = (ans + (sum * qz[e[j].to]) % mo) % mo;
sum = (sum + qz[e[j].to]) % mo;
}
}
printf("%lld %lld", maxn, ans);
fclose(stdin);
fclose(stdout);
return 0;
}