1. 程式人生 > >BZOJ5072[Lydsy十月月賽] 小A的樹 解題報告【樹上揹包/樹形DP】

BZOJ5072[Lydsy十月月賽] 小A的樹 解題報告【樹上揹包/樹形DP】

Problem Statement
小A 成為了一個園藝家!他有一棵n 個節點的樹(如果你不知道樹是什麼,請看Hint 部分)。他不小心打翻了墨水瓶,使得樹的一些節點被染黑了。小A 發現這棵染黑了的樹很漂亮,於是想從樹中取出一個x 個點的聯通子圖,使得這些點中恰有y 個黑點,他想知道他的願望能否實現。可是他太小,不會算,請你幫幫他。
解題報告
這道題可以理解為:有n個點,每個點有其點權,一些點的點權是1,一些點的點權是0,選擇每一個點都有代價,代價為1,問能否用大小為x的揹包裝下權值為y的點。
關於樹上揹包,有這麼一篇部落格,這裡我們不僅算出最多能選的價值,也算出最小能選到的價值就好了。
具體的狀態:

dp[u][j+k]//以u為根節點,一個子樹選k個點,其他子樹選u個點的最小价值
g[u][j+k]//以u為根節點,一個子樹選k個點,其他子樹選u個點的最大價值

轉移:

dp[u][j+k]=min(dp[u][j+k],dp[u][j]+dp[v][k]),g[u][j+k]=max(g[u][j+k],g[u][j]+g[v][k]);

dp陣列初值賦+inf,g陣列賦0,搜尋到每一個點的時候更改dp[u][1]/dp[v][1]。
程式碼如下:

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std; const int N=5000; struct edge { int v,next; }ed[2*N+5]; int T,n,q; int w[N+5],size[N+5]; int dp[N+5][N+5],g[N+5][N+5]; int vmax[N+5],vmin[N+5]; int head[N+5],num; void build(int u,int v) { ed[++num].v=v; ed[num].next=head[u]; head[u]=num; } void dfs(int u,int f) { size[u]=1
; dp[u][1]=w[u],g[u][1]=w[u]; for(int i=head[u];i!=-1;i=ed[i].next) { int v=ed[i].v; if(v==f)continue; dfs(v,u); for(int j=size[u];j;j--) for(int k=0;k<=size[v];k++) dp[u][j+k]=min(dp[u][j+k],dp[u][j]+dp[v][k]), g[u][j+k]=max(g[u][j+k],g[u][j]+g[v][k]); size[u]+=size[v]; } for(int i=1;i<=n;i++)vmin[i]=min(vmin[i],dp[u][i]),vmax[i]=max(vmax[i],g[u][i]); } void init() { memset(head,-1,sizeof(head));num=0; memset(dp,0x3f,sizeof(dp)); memset(vmin,0x3f,sizeof(vmin)); memset(vmax,0,sizeof(vmax)); memset(g,0,sizeof(g)); } int main() { for(scanf("%d",&T);T;T--) { init(); scanf("%d%d",&n,&q); for(int i=1;i<n;i++) { int u,v; scanf("%d%d",&u,&v); build(u,v); build(v,u); } for(int i=1;i<=n;i++)scanf("%d",&w[i]); dfs(1,0); for(int i=1;i<=q;i++) { int x,y; scanf("%d%d",&x,&y); if(y>=vmin[x]&&y<=vmax[x])printf("YES\n"); else printf("NO\n"); } printf("\n"); } return 0; }