BZOJ5072[Lydsy十月月賽] 小A的樹 解題報告【樹上揹包/樹形DP】
阿新 • • 發佈:2019-02-15
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;
}