很典型的樹形DP,自己也理解了好久,感覺自己好水哦。。。。。。。。。。。。。。。。。。。。。。。。。
所以講得清楚一點,以后回憶起來也快
題意:一顆樹,n個點(1-n),n-1條邊,每個點上有一個權值,求從1出發,走V步,最多能遍歷到的權值
我們把背包的思想用到這里來,做的步數相當於背包的容量,點上的權值相當於價值,給定一定的背包容量,求最多能裝進背包的價值
設dp[0][s][j]表示從s(當前根節點)出發,走 j 步,回到s所能獲得的最大權值
dp[1][s][j]表示從s(當前根節點)出發,走j步,不回到s所能獲得的最大權值
現在我們就可以分配背包容量了:父節點與子節點分配背包容量,從而設計出狀態轉移方程
主要思想:
s返回,t返回
s不返回,t返回(走向t子樹,t子樹返回之后走向s的其他子樹,然后不回到s)
s返回,t不返回(遍歷s的其他子樹后返回s,返回之后走向t子樹,然后不回到t)
沒有都不返回,肯定有一方有一個返回的過程,再去另一邊的子樹的
總結起來一句話,要么去s的其他子樹呆着,要么去t子樹呆着,要么回到s點
1、在t子樹返回,其他子樹也返回,即回到當前根節點s
2,、不返回根節點,但在t子樹返回,即相當於從t出發走k步返回t的最優值 加上 從s出發走j-k步到其他子樹不返回的最優值,中間有s與t連接起來,其實就等於從s出發遍歷t子樹后(dp[0][t][k])又回到s(這一步多了中間的來回兩步),再走出去(其他子樹)【dp[1][s][j-k]】,不回來
3、不返回根節點,在t子樹也不返回,等價於從s出發遍歷其他子樹,回到s(dp[0][s][j-k]),再走向t子樹,不回到t(dp[1][t][k]),這個過程s-t只走了一步
dp[0][s][j+2]=Max(dp[0][s][j+2],dp[0][t][k]+dp[0][s][j-k]);//從s出發,要回到s,需要多走兩步s-t,t-s,分配給t子樹k步,其他子樹j-k步,都返回
dp[1][s][j+2]=Max(dp[1][s][j+2],dp[0][t][k]+dp[1][s][j-k]);//不回到s(去s的其他子樹),在t子樹返回,同樣有多出兩步
dp[1][s][j+1]=Max(dp[1][s][j+1],dp[1][t][k]+dp[0][s][j-k]);//先遍歷s的其他子樹,回到s,遍歷t子樹,在當前子樹t不返回,多走一步

#include<cstdio>
#include<cstring>
#include<vector>
using namespace std;
vector<int> g[1010];
int dp[2][1010][210];
int n,V,val[1010];
bool used[1010];
void Max(int &a,int b){
if(a<b) a=b;
}
void dfs(int s){
int i;
used[s]=true;
for(i=0;i<=V;i++){
dp[0][s][i]=dp[1][s][i]=val[s];//強制加入
}
for(i=0;i<g[s].size();i++){
int t=g[s][i];
if(used[t]) continue;
dfs(t);
for(int j=V;j>=0;j--){
for(int k=0;k<=j;k++){//中間的橋梁一定要走,所以把狀態依次轉變過來
Max(dp[0][s][j+2],dp[0][t][k]+dp[0][s][j-k]);
Max(dp[1][s][j+2],dp[0][t][k]+dp[1][s][j-k]);
Max(dp[1][s][j+1],dp[1][t][k]+dp[0][s][j-k]);
}
}
}
}
int main(){
int i,a,b;
while(scanf("%d%d",&n,&V)!=EOF){
for(i=0;i<=1000;i++)
g[i].clear();
for(i=1;i<=n;i++)
scanf("%d",&val[i]);
for(i=0;i<n-1;i++){
scanf("%d%d",&a,&b);
g[a].push_back(b);
g[b].push_back(a);
}
memset(dp,0,sizeof(dp));
memset(used,false,sizeof(used));
dfs(1);
printf("%d\n",dp[1][1][V]);
}
return 0;
}