Description
有一棵 (n) 个点的树,从 (1) 号点出发,经过一条边的时候会减少一定的 HP,每条边最多只能经过两次。首次到达某个点 (i) 会增加 (a[i]) 点 HP。求初始 HP 至少为多少才能保证存在一种能遍历所有点并回到原点的方案且整个过程中的 HP 非负。
Solution
不妨设访问 (i) 的子树的代价总和为 (s[i]),设访问 (i) 的子树过程中可能出现的最小 HP 的最大值(可能是负数)为 (f[i]),则转移时需要枚举访问各个子树的顺序。
考虑引入临项交换排序的思想,对于 (i,j=i+1),如果 (i o j) 比 (j o i) 优,那么一定有 (min(f_j, s_j+f_i) = min (f_i,s_i+f_j))。
为了能分离出排序使用的关键字,暴力展开上式,发现可以将所有孩子分成 (A={i | i ge 0}, B=E-A) 两部分,那么显然对于 (a in A,b in B),(a o b) 是更优的,于是只需要考虑 (A,B) 各自内部的顺序。根据展开结果容易得出,(A) 内需要 (f_i > f_j),(B) 内需要 (s_i - f_i > s_j - f_j)。实际实现时可以放在一起处理。
#include <bits/stdc++.h>
using namespace std;
#define int long long
const int N = 1000005;
int f[N],s[N],a[N],vis[N],n,m,faw[N];
vector <pair<int,int>> g[N];
int t1,t2,t3;
void clear()
{
for(int i=1;i<=n;i++)
{
f[i]=s[i]=a[i]=vis[i]=faw[i]=0;
g[i].clear();
}
}
bool cmp(int i,int j)
{
if(s[i]>=0 && s[j]<0) return 1;
if(s[i]>=0)
{
return f[i]>f[j];
}
if(s[j]<0)
{
return s[i]+f[j]>s[j]+f[i];
}
return 0;
}
void dfs(int p)
{
vis[p]=1;
s[p]=a[p];
vector <int> vec;
for(auto pr:g[p])
{
int q=pr.first,w=pr.second;
if(vis[q]==0)
{
dfs(q);
s[q]-=w*2;
f[q]-=w;
f[q]=min(f[q],s[q]);
vec.push_back(q);
}
}
sort(vec.begin(),vec.end(),cmp);
for(auto i:vec)
{
f[p]=min(f[p],s[p]+f[i]);
s[p]+=s[i];
}
}
signed main()
{
ios::sync_with_stdio(false);
int t;
cin>>t;
while(t--)
{
cin>>n;
for(int i=1;i<=n;i++) cin>>a[i];
for(int i=1;i<n;i++)
{
cin>>t1>>t2>>t3;
g[t1].push_back({t2,t3});
g[t2].push_back({t1,t3});
}
dfs(1);
cout<<max(0ll,-f[1])<<endl;
clear();
}
return 0;
}