【16年浙江省賽 B ZOJ 3937】More Health Points 題解
阿新 • • 發佈:2022-04-04
題目連結
\(Describe\)
給定一棵以節點\(1\)為根的樹,節點間單向邊連線,每個節點有權值\(B_i\),找到其中一串子鏈(也可以為空),該子鏈最靠近\(1\)的節點為該子鏈的根節點,計算每個節點的深度(根節點深度為\(1\))與權值的乘積,並求和,即\(ans= \sum\limits_{i=1}^ndeep_i*B_i\)(其中\(n\)表示該子鏈節點數,\(deep_i\)表示第\(i\)個節點的深度),輸出最大的和(若所選子鏈為空,和為\(0\))
\(Solution\)
這道題是大一時我在校集訓隊\(vp\)訓練時做的一道題,當時只有我們隊出了這題,而我是負責這題的,也正是託這題的福,我們成功拿下了當時榜單\(rank1\)
賽後我去網上搜了一下這個題,結果呢,好傢伙,居然這麼難?好多部落格的題解有斜率優化\(dp\)、動態維護下凸殼、凸包凸殼等我不會的演算法,當時一臉懵逼,因為在\(vp\)時,我根本沒有想這麼多,我就單純樹形\(dp\)跑\(dfs\)就\(AC\)了這題。
具體講講我的做法,這題我的第一想法就是跑\(dfs\),最開始我是從根節點\(1\)開始\(dfs\),但是\(TLE\)了,演算法也會變得相當複雜。於是,我換了個思路,反向建邊,從葉子節點跑\(dfs\)到根節點,這樣對於答案求和的計算也會非常簡單,用\(sum[i]\)表示從葉子節點到\(i\)節點的\(B_i\)
\(dp[v]=dp[u]+sum[v]\)
\(dp[v]=max(dp[v],B[v])\)
另外,要一邊跑\(dfs\)一邊維護字首和\(sum\)的值。但是,這樣子去寫會\(TLE\),於是我開始了剪枝優化,對於一些已經跑過了的點,可以省略不跑,用\(vis\)陣列記錄跑過的點,如果滿足
\(vis[v]=1,dp[u]+a[v]+sum[u]\le dp[v],sum[u]+a[v]\le sum[v]\)
就可以停止\(dfs\)了,直到把所有的葉子節點跑完,答案輸出為\(dp\)陣列的最大值,最小輸出\(0\)
\(Code\)
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
ll t,n,a[100005],b,head[100005],tot,ans,dp[100005],sum[100005],in[100005],vis[100005];
struct node
{
ll v,next;
}e[100005];//鏈式前向星存圖
void add(ll u,ll v)
{
e[++tot].v=v;
e[tot].next=head[u];
head[u]=tot;
}//加邊操作
void dfs(ll u)
{
vis[u]=1;
if(head[u]){ll v=e[head[u]].v;//因為從葉子節點出發能到的點最多隻有1個,所以相當於一條鏈,不需要for迴圈
if(vis[v]==1&&dp[u]+a[v]+sum[u]<=dp[v]&&sum[u]+a[v]<=sum[v])return;//此解法的關鍵,剪枝優化,否則TLE
else
{
sum[v]=a[v]+sum[u],dp[v]=dp[u]+sum[v],ans=max(ans,max(dp[v],a[v]));//狀態轉移,記錄最大答案
if(dp[v]>a[v])dfs(v);
else
{
dp[v]=a[v];
sum[v]=a[v];
dfs(v);
}
}
}
}
signed main()
{
scanf("%lld",&t);
while(t--)
{
memset(head,0,sizeof head);
memset(in,0,sizeof in);
memset(vis,0,sizeof vis);
memset(dp,0x80,sizeof dp);
ans=tot=0;
scanf("%lld",&n);
for(ll i=1;i<=n;++i)scanf("%lld",&a[i]);
for(ll i=2;i<=n;++i)
{
scanf("%lld",&b);
add(i,b);//反向建邊
in[b]++;//有點類似拓撲排序,其實就是記錄葉子節點
}
for(ll i=1;i<=n;++i)if(in[i]==0)
{
sum[i]=a[i];
dp[i]=a[i];
dfs(i);
}
printf("%lld\n",ans);
}
return 0;
}
看了其他解法的題解程式碼都比較長,有上百行,我的程式碼只有\(60\)行\((qwq)\)