洛谷题单指南-图论之树-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;
}
浙公网安备 33010602011771号