[NOIP2014 提高组] 联合权值 题解

发布于 2023-03-04  343 次阅读


[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 200000, 0 < W_i \leq 10000$。

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

题解

一道可以用初中数学平方和公式优化的题(我真笨

本题让我们求最大的联合权值以及联合权值之和,我们暴力枚举每个点,然后直接统计。但是求联合权值之和时,我们要两层循环把每个节点所有相连的节点两两相乘,时间复杂度$O(n^2)$。

回想一下平方和公式:

$$(a+b)^2=a^2 + b^2 \times 2ab$$

变一下位置就是

$$2ab = (a+b)^2-(a^2 + b^2)$$

同理三个点就是

$$2ab+2bc+2ac=(a+b+c)^2-(a^2+b^2+c^2)$$

所以只要把当前的点相连的权值和的平方减去平方的和即可。

code

#include <iostream>
using namespace std;

const int MAXN = 200005;
const int MOD = 10007;
struct node
{
    int to;
    int next;
} edge[MAXN << 1];
int head[MAXN], n, cnt;
long long ans_max = 0, ans_sum = 0, w[MAXN];

inline void add_edge(int from, int to)
{
    edge[++cnt] = (node){to, head[from]};
    head[from] = cnt;
}

int main()
{
    std::ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);

    cin >> n;
    for (int i = 1, x, y; i < n; i++)
    {
        cin >> x >> y;
        add_edge(x, y);
        add_edge(y, x);
    }
    for (int i = 1; i <= n; i++)
        cin >> w[i];

    for (int i = 1; i <= n; i++)
    {
        long long max1 = 0, max2 = 0, cnt1 = 0, cnt2 = 0;
        for (int j = head[i]; j; j = edge[j].next)
        {
            int now_node = edge[j].to;
            if (w[now_node] > max1)      // 求最大的两个数
                max1 = w[now_node];
            else if (w[now_node] > max2)
                max2 = w[now_node];
            cnt1 += w[now_node] % MOD;
            cnt2 += w[now_node] * w[now_node] % MOD;
        }
        ans_max = max(max1 * max2, ans_max);    // 最大的联合权值
        ans_sum += (cnt1 * cnt1 - cnt2) % MOD;
        ans_sum = ans_sum % MOD;    //联合权值之和
    }
    cout << ans_max << " " << ans_sum % MOD << endl;
    return 0;
}

$end$