P1351 [NOIP2014 提高组] 联合权值
[NOIP2014 提高组] 联合权值
题目背景
NOIP2014 提高组 D1T2
题目描述
无向连通图 \(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 2\times 10^5\),\(0 < W_i \leq 10000\)。
保证一定存在可产生联合权值的有序点对。
可以暴力写也可以用树形dp写
暴力:
我们对于每个节点来说可以看它的直接子节点,计sum为节点权值之和,则改节点对于所有联合权值之和的贡献就是\(sum^2-\sum_{i=1}^na[i]^2\),这个其实很简单,对于某一个直接子节点x来说,它的贡献是\(x*(sum-x)\),转换一下就变成了\(sum*x-x^2\),然后对每一个点这样计算就好了
而找最大的话就是找最大的和第二大的两个数去算就好了
代码
vector<int> g[N];
void tt()
{
int n;
cin >> n;
vector<int> a(n + 1);
for (int i = 1; i < n; i++)
{
int x, y;
cin >> x >> y;
g[x].push_back(y), g[y].push_back(x);
}
for (int i = 1; i <= n; i++)
cin >> a[i];
int ans = 0;
int mx = 0;
for (int i = 1; i <= n; i++)
{
if (g[i].size() <= 1)
continue;
vector<int> ve;
int sum = 0;
for (int l = 0; l < g[i].size(); l++){
ve.push_back(a[g[i][l]]);
sum += a[g[i][l]];
ans -= a[g[i][l]] * a[g[i][l]];
}
sort(ve.begin(), ve.end());
mx = max(mx, ve[ve.size() - 1] * ve[ve.size() - 2]);
ans += sum * sum;
ans %= mod;
}
cout << mx << " ";
cout << ans << endl;
}
树形dp
感觉差不多,就是把\(sum^2-\sum_{i=1}^na[i]^2\)展开,然后再减少一些重复遍历就好了
const int N = 500005;
vector<int> g[N];
vector<int> a(N);
int ans = 0;
int an = 0;
int mx[N];//记录当前点的直接子节点的最大值
void dfs(int now, int fa)
{
if (g[now].size() == 1 && fa != 0)
return;
mx[now] = a[fa];
int sum = a[fa];
sum %= mod;
for (auto t : g[now])
{
if (t == fa)
continue;
dfs(t, now);
an = max(an, mx[now] * a[t]); // 找联合权值最大
mx[now] = max(mx[now], a[t]);//找当前点的最大直接子节点
ans += sum * a[t];// 该点对所有联合权值之和的贡献(这里只有一半,因为它反过来也算,1 3 和3 1个算一次)
ans %= mod;
sum += a[t];
sum %= mod;
}
}
void tt()
{
int n;
cin >> n;
for (int i = 1; i < n; i++)
{
int x, y;
cin >> x >> y;
g[x].push_back(y), g[y].push_back(x);
}
for (int i = 1; i <= n; i++)
cin >> a[i];
dfs(1, 0);
cout << an << " " << ans * 2 % mod << endl;//记得乘2后取余,因为它反过来也算,1 3 和3 1个算一次
}