这道题算法很好想,为了不枚举每一个点做一遍,因此考虑换根dp,也就是先正的做一遍,之后从上面来更新下面的节点,难点在于细节非常复杂。
首先我们可以想到维护的是f[i][],g[i][],表示子树和上方的信息
分为f[i][0],和f[i][1]表示取完后回到i以及在i的某个子树中停留。
剩下的更新方程十分复杂,我在代码旁边有着注释,便于理解。
#include<bits/stdc++.h> using namespace std; typedef long long ll; typedef unsigned long long ull; typedef pair<ll,ll> pll; const int N=5e5+10; const int inf=0x3f3f3f3f; const ll mod=998244353; int a[N],n; int h[N],ne[N],e[N],w[N],idx; int f[N][2],g[N][2]; int id[N]; void add(int a,int b,int c){ e[idx]=b,ne[idx]=h[a],w[idx]=c,h[a]=idx++; } void dfs(int u,int fa){ f[u][0]=f[u][1]=a[u]; int i; for(i=h[u];i!=-1;i=ne[i]){ int j=e[i]; if(j==fa) continue; dfs(j,u); f[u][1]+=max(0,f[j][0]-2*w[i]);//含义是停留的地方不变,因此当前的子树如果大于0需要走 if(f[u][0]+f[j][1]-w[i]>f[u][1]){//更改停留地点为当前子树上的点,如果更优,则更换 f[u][1]=f[u][0]+f[j][1]-w[i]; id[u]=j; } f[u][0]+=max(0,f[j][0]-2*w[i]); } } void get(int u,int fa){ if(f[u][0]+g[u][1]>f[u][1]+g[u][0]){ id[u]=fa; } int i; for(i=h[u];i!=-1;i=ne[i]){ int j=e[i]; if(j==fa) continue; if(j==id[u]){ int tmp1=g[u][1]+a[u],tmp2=g[u][0]+a[u];//tmp1目前是父亲往上的停留点,tmp2目前是回到父亲的点 for(int k=h[u];k!=-1;k=ne[k]){ int v=e[k]; if(v==j||v==fa) continue; tmp1+=max(0,f[v][0]-2*w[k]); if(tmp2+f[v][1]-w[k]>tmp1){ tmp1=tmp2+max(0,f[v][1]-w[k]); } tmp2+=max(0,f[v][0]-2*w[k]); } g[j][0]=max(0,tmp2-2*w[i]);//回到当前点的最大值 g[j][1]=max(0,tmp1-w[i]);//停留在上面的最大值 } else{ int tmp; //tmp是因为我们做的是上方的信息,因此要取消这个子树的信息 if(f[j][0]>=2*w[i]){ tmp=f[j][0]-2*w[i]; } else{ tmp=0; } g[j][0]=max(0,f[u][0]+g[u][0]-tmp-2*w[i]); g[j][1]=max(0,max(g[u][1]+f[u][0]-tmp-w[i],f[u][1]+g[u][0]-tmp-w[i])); } get(j,u); } } int main(){ ios::sync_with_stdio(false); int t; cin>>t; int cnt=1; while(t--){ cin>>n; int i; idx=0; for(i=0;i<=n;i++){ h[i]=-1; f[i][1]=f[i][0]=g[i][1]=g[i][0]=0; id[i]=-1; } for(i=1;i<=n;i++) cin>>a[i]; for(i=1;i<n;i++){ int a,b,c; cin>>a>>b>>c; add(a,b,c); add(b,a,c); } dfs(1,-1); get(1,-1); cout<<"Case #"<<cnt++<<":"<<endl; for(i=1;i<=n;i++){ cout<<(max(f[i][0]+g[i][1],f[i][1]+g[i][0]))<<endl; } } return 0; }