『题解』洛谷P1351 联合权值

更好的阅读体验

Portal

Portal1: Luogu

Portal2: LibreOJ

Description

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

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

Input

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

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

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

Output

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

Sample Input

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

Sample Output

20 74

Solution

我们先看一下题目:无向连通图\(\mathrm G\)\(n\)个点,\(n - 1\)条边。

不难发现题目给出的是一颗树。

我们看一个例子:

这个图的联合权值和为\(W_2 \times W_3 + W_4 \times W_5 + W_4 \times W_6 + W_5 \times W_6 + W_7 \times W_8\)

不难发现,我们求的是对于每一棵子树的非根节点的所有子结点两两相乘的权值和。但是我们对每一棵子树都遍历一遍显然要超时。我们可以找到如下性质:

\((a + b) ^ 2 = a ^ 2 + b ^ 2 + 2ab​ \\\ (a + b + c) ^ 2 = a ^ 2 + b ^ 2 + c ^ 2 + 2ab+ 2ac + 2bc \\\ (a + b + c + d) = a ^ 2 + b ^ 2 + c ^ 2 + d ^ 2 + 2ab+ 2ac + 2ad + 2bc + 2bd + 2cd \\\ \cdots \cdots\)

我们要求的就是平方项后面的一半。就是 \(\texttt{和的平方} - \texttt{平方的和}\)

统计最大值是只需要找出最大的两项,然后相乘就可以了。

这样就这道题就解决了。

Code

#include<iostream>
#include<algorithm>
#include<cstdio>
#include<cstring>
#include<cmath>

using namespace std;

typedef long long LL;
const int INF = 0x3f3f3f3f, MAXN = 400005, MAXM = 200005, mod = 10007;
struct EDGE {
    int to, nxt;
} edge[MAXN];
int n, u, v, cnt, w[MAXM], head[MAXN];
inline void addedge(int u, int v) {
    edge[++cnt].to = v; edge[cnt].nxt = head[u]; head[u] = cnt;
}
int main() {
    scanf("%d", &n);
    memset(head, -1, sizeof(head));
    for (int i = 1; i < n; i++) {
        scanf("%d%d", &u, &v);
        addedge(u, v); addedge(v, u);//加边
    }
    for (int i = 1; i <= n; i++)
        scanf("%d", &w[i]);
    LL Max = -INF, ans = 0;
    for (int i = 1; i <= n; i++) {
        LL Max1 = -INF, Max2 = -INF, tot1 = 0, tot2 = 0;//Max1表示最大的权值,Max2表示第二大的权值,tot1表示和的平方,tot2表示平方的和
        for (int j = head[i]; ~j; j = edge[j].nxt) {//遍历每一个点
            if (w[edge[j].to] > Max1) {
                Max2 = Max1;
                Max1 = w[edge[j].to];
            } else
            if (w[edge[j].to] > Max2 && w[edge[j].to] <= Max1) Max2 = w[edge[j].to];//找两个最大的
            tot1 += w[edge[j].to]; tot2 = (tot2 + w[edge[j].to] * w[edge[j].to]) % mod;//累计当前点的权值
        }
        tot1 = (tot1 % mod * tot1 % mod) % mod;//和的平方
        ans = (ans + tot1 - tot2 + mod) % mod;//累加答案
        Max = max(Max, Max1 * Max2);//找最大权值
    }
    printf("%lld %lld\n", Max, ans);
    return 0;
}

Attachment

测试数据下载:https://www.lanzous.com/i5q1vdg

posted @ 2019-08-22 10:39  XiaoHuang666  阅读(149)  评论(0编辑  收藏  举报