题面
这是2022年ICPC昆明站的F题。在赛场上,我一开始敲了个贪心,但是出锅了,改敲树形DP,但是时间来不及了。在队友的提醒下补过了这个题,知道解法的我发现我就是个纯纯的老坛……
原题链接在牛客网:F-Find the Maximum_第46届ICPC亚洲区域赛(昆明)(正式赛),需要先登录之后才能访问。下面搬运一下原题面:
A tree with nn vertices is a connected undirected graph with n
n vertices and n−1n−1 edges.You are given a tree with n
n vertices. Each vertex has a value bibi . Note that for any two vertices there is exactly one single path between them, whereas a simple path doesn't contain any edge more than once. The length of a simple path is considered as the number of edges in it.You need to pick up a simple path whose length is not smaller than 1
1 and select a real number xx . Let VV be the set of vertices in the simple path. You need to calculate the maximum of ∑u∈V(−x2+bux)|V|∑u∈V(−x2+bux)|V| .输入描述
The first line contains a single integer n(2≤n≤105)
n(2≤n≤105) , indicating the number of vertices in the tree.The second line contains n
n integers b1,b2,⋯,bn(10−5≤bi≤105)b1,b2,⋯,bn(10−5≤bi≤105) indicating the values of each vertex.Each line in the next n−1
n−1 lines contains two integers u,vu,v indicating an edge in the tree.输出描述
The output contains a single real number, indicating the answer.
Your answer will be accepted if and only if the absolute error between your answer and the correct answer is not greater than 10−4
10−4 .时空限制
时间限制:C/C++ 1秒,其他语言2秒
空间限制:C/C++ 262144K,其他语言524288K输入
2 3 2 1 2输出
1.562500
大意
给出一颗无向树,树上的每一个节点都有一个权值bi
题解
本题权重可正可负,而最后需要求的式子是平均值的平方,因此在求解的时候需要同时考虑最大和最小两种情况。而要想路径上的节点算术平均值最大,路径的长度只能为1或者2。
为什么呢?假如有一条长度为3的路径L:v1−v2−v3−v4
我们来简单说明一下:假设wi
t=w1+w2+w3+w44=w1+w2+w3+w42×12=(w1+w22+w3+w42)×12=t1+t22
也就是说t
长度为1的路径边读入边处理即可,长度为2的路径可以通过一轮DFS来寻找:若指定一个节点为DFS的起点,则可以按照各节点到起点的距离将其视作一颗有向树,那么长度为2的路径就有两种情况:
(1)当前节点、当前节点的父节点以及当前节点的子节点,路径为父节点-当前节点-子节点
(2)当前节点和它的两个子节点,路径为子节点-当前节点-另一个子节点。
第一种情况可以很方便的枚举,而第二种情况,由于求的是最值,所以我们可以对所有的子节点排序,贪心地选择权值最大的前两个子节点和权值最小的前两个子节点进行讨论就可以了。
对于子节点的排序时间复杂度为O(nlogn)
代码实现如下:
#include <bits/stdc++.h>
#define GRP int T;cin>>T;rep(C,1,T)
#define FAST ios::sync_with_stdio(false);cin.tie(0);
#define rep(i,a,b) for(int i=a;i<=b;++i)
#define rrep(i,a,b) for(int i=a;i>=b;--i)
#define elif else if
#define mem(arr,val) memset(arr,val,sizeof(arr))
typedef long long ll;
typedef unsigned long long ull;
using namespace std;
int n;
double w[100010]; //权值
double ans;
vector< vector<int> > e; //vector实现邻接表
int u, v;
void dfs(int r, int pre) {
//将所有的子节点按照权值排序
sort(e[r].begin(), e[r].end(), [](int a, int b)->bool{
return w[a] > w[b];
});
//如果子节点有至少两个,就考虑子节点-当前节点-子节点这样的路径(以无向树存储,DFS只是视作有向树,因此需要排除掉父节点)
if (e[r].size() > 2 || (pre < 1 && e[r].size() > 1)) {
int cnt = 0, flag = 0;
double cur = w[r];
//挑权值最大的两个节点
while (cnt != 2) {
//如果其中一个是父节点
if (e[r][flag] == pre) {
++flag;
continue;
}
cur += w[e[r][flag]];
++flag;
++cnt;
}
cur /= 3;
ans = max(ans, cur * cur / 4); //更新答案
cnt = 0, flag = e[r].size() - 1;
cur = w[r];
//挑权值最小的两个节点
while (cnt != 2) {
if (e[r][flag] == pre) {
--flag;
continue;
}
cur += w[e[r][flag]];
--flag;
++cnt;
}
cur /= 3;
ans = max(ans, cur * cur / 4);
}
for (int i : e[r]) {
if (i == pre) {
continue; //不能回头
}
//不是起始节点的情况下,考虑父节点-当前节点-子节点的路径
if (pre > 0) {
double t = (w[pre] + w[r] + w[i]) / 3;
ans = max(ans, t * t / 4); //更新答案
}
dfs(i, r); //继续搜索
}
}
int main() {
FAST
cin >> n;
e.resize(n + 1);
ans = 0;
rep(i, 1, n) {
cin >> w[i];
}
rep(i, 1, n - 1) {
cin >> u >> v;
e[u].push_back(v);
e[v].push_back(u);
double t = (w[u] + w[v]) / 2; //长度为1的边读入时就可以进行处理
ans = max(ans, t * t / 4);
}
dfs(1, -1); //指定起始节点为第一个节点,并且指定其父节点不存在
cout << fixed << setprecision(6) << ans << endl;
return 0;
}
/*
_ _ _ _
/\ | | | | | | (_)
/ \ | | _____ _| |__| | ___ _ __ _ _ __ __ _
/ /\ \ | |/ _ \ \/ / __ |/ _ \| '__| | '_ \ / _` |
/ ____ \| | __/> <| | | | (_) | | | | | | | (_| |
/_/ \_\_|\___/_/\_\_| |_|\___/|_| |_|_| |_|\__, |
__/ |
|___/
*/
__EOF__