洛谷题单指南-图论之树-P1351 [NOIP 2014 提高组] 联合权值

原题链接:https://www.luogu.com.cn/problem/P1351

题意解读:找到所有距离为2的点对,(a,b)(b,a)是两组点对,每组点对贡献一个联合权值w[a] * w[b],求最大的联合权值,以及所有联合权值之和%10007。

解题思路:

直觉上,直接枚举所有节点,然后在每个节点基础上向下找深度2的节点,更新答案即可,不过经分析这样做复杂度最坏可能接近O(n^2),显然不可行,但是可以拿一定分数。

70分代码:

#include <bits/stdc++.h>
using namespace std;

const int N = 200005, MOD = 10007;

vector<int> g[N];
int w[N];
int ans1, ans2, start;
int n;

void dfs(int u, int p, int d)
{
    
    if(d == 2) 
    {
        ans1 = max(ans1, w[start] * w[u]);
        ans2 = (ans2 + w[start] * w[u]) % MOD;
        return;
    }
    for(auto v : g[u])
    {
        if(v == p) continue;
        dfs(v, u, d + 1);
    }
}

int main()
{
    cin >> n;
    for(int i = 1; i < n; i++)
    {
        int u, v;
        cin >> u >> v;
        g[u].push_back(v);
        g[v].push_back(u);
    }
    for(int i = 1; i <= n; i++) cin >> w[i];
    for(start = 1; start <= n; start++)
    {
        dfs(start, 0, 0);
    }
    cout << ans1 << " " << ans2;
    return 0;
}

稍加思考,可以发现,所有点对都是某个点的邻接点,那何不枚举所有点,然后看邻接点,邻接点两两配对即组成所有点对,那么如何求值?

对于一个点的所有邻接点

要取联合权值最大的点对,显然只用找两个权值最大的点,通过一次遍历即可找到最大值和次大值。

要计算所有点对联合权值对和的贡献,设有三个邻接点权值为a、b、c,对和的贡献是2ab+2bc+2ac,如何通过一次遍历得到这个结果?

我们知道,(a+b+c)^2 - (a^2+b^2+c^2) = 2ab+2bc+2ac,通过一次遍历可以得到a+b+c、a^2+b^2+c^2,同时也可以得到最大值和次大值,对于邻接点多于3个情况也是一样,这样问题就解决了。

整体时间复杂度O(n)。

100分代码:

#include <bits/stdc++.h>
using namespace std;

const int N = 200005, MOD = 10007;

vector<int> g[N];
int w[N];
int ans1, ans2;
int n;

int main()
{
    cin >> n;
    for(int i = 1; i < n; i++)
    {
        int u, v;
        cin >> u >> v;
        g[u].push_back(v);
        g[v].push_back(u);
    }
    for(int i = 1; i <= n; i++) cin >> w[i];
    for(int i = 1; i <= n; i++)
    {
        if(g[i].size() >= 2)
        {
            int sum1 = 0, sum2 = 0; //i邻接点所有值的和的平方、平方和
            int max1 = 0, max2 = 0; //i邻接点中的最大值、次大值
            for(auto j : g[i])
            {
                sum1 += w[j]; sum1 %= MOD;
                sum2 += w[j] * w[j]; sum2 %= MOD;
                if(w[j] >= max1) max2 = max1, max1 = w[j];
                else if(w[j] > max2) max2 = w[j];
            }
            ans1 = max(ans1, max1 * max2); //只取值最大的两个点即可
            sum1 = sum1 * sum1 % MOD;
            //i所有邻接点两两配对可对答案产生贡献,总贡献根据数学公式求解
            //(a+b+c...)^2 - (a^2+b^2+c^2...) = 2ab+2bc+2ac...
            ans2 += sum1 - sum2; 
            ans2 = (ans2 % MOD + MOD) % MOD; //防止负数
        }
    }
    cout << ans1 << " " << ans2;
    return 0;
}

 

posted @ 2025-03-18 14:53  hackerchef  阅读(25)  评论(0)    收藏  举报