題意
給一棵樹,樹上每個點有兩個權值,分別是wi,bi
要求將樹分成m個聯通塊,要求最多能有幾個聯通塊內的
數據範圍
多組數據,組數<=100
n,m<=3000
解法
樹形揹包
首先有一個簡單的想法:f[i][j][k]表示第i個點的子樹,被分成了j個聯通塊,其中有k個聯通塊滿足要求,此時與i相連的聯通塊的權值最大是多少.
用子樹揹包的方式轉移可以做到
然後考慮如果同一棵子樹,劃分聯通塊的數量一樣,那麼如果k的差距比較大,則k較小的那個狀態是永遠不會起到作用的.然後可以考慮把第三維省掉:
變成dp[i][j]表示第i個點的子樹,被分成了j個聯通塊時,最多有幾個塊滿足條件,同時和i相連的塊的大小最大是多少.
轉移就枚舉一個兒子v,討論要不要把v所在的聯通塊和i所在的聯通塊合併,就可以了.
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn=3e3+5;
inline int read(){
char c=getchar();int t=0,f=1;
while((!isdigit(c))&&(c!=EOF)){if(c=='-')f=-1;c=getchar();}
while(isdigit(c)&&(c!=EOF)){t=(t<<3)+(t<<1)+(c^48);c=getchar();}
return t*f;
}
int t,n,m,b[maxn],w[maxn],a[maxn];
vector<int> e[maxn];
inline void add(int a,int b){
e[a].push_back(b);
e[b].push_back(a);
}
typedef pair<int,ll> pii;
pii f[maxn][maxn],tmp[maxn];
int sz[maxn];
inline void update(pii &a,pii b){
if(a.first<b.first){a=b;}
else if(a.first==b.first&&a.second<b.second){a=b;}
}
void dfs(int u,int fa){
sz[u]=1;f[u][1]=pii(0,a[u]);
for(int i=0;i<e[u].size();i++){
int v=e[u][i];
if(v==fa)continue;
dfs(v,u);
for(int j=1;j<=sz[u]+sz[v];j++)tmp[j]=pii(-1,0);
for(int j=1;j<=sz[u];j++){
for(int k=1;k<=sz[v];k++){
update(tmp[j+k],pii(f[u][j].first+f[v][k].first+(f[v][k].second>0),f[u][j].second));
update(tmp[j+k-1],pii(f[u][j].first+f[v][k].first,f[u][j].second+f[v][k].second));
}
}
for(int j=1;j<=sz[u]+sz[v];j++)f[u][j]=tmp[j];
sz[u]+=sz[v];
}
}
int main(){
t=read();
while(t--){
for(int i=1;i<=n;i++)e[i].clear();
n=read(),m=read();
for(int i=1;i<=n;i++)b[i]=read();
for(int i=1;i<=n;i++)w[i]=read();
for(int i=1;i<=n;i++)a[i]=w[i]-b[i];
for(int i=1;i<n;i++){
int a,b;
a=read(),b=read();
add(a,b);
}
dfs(1,0);
printf("%d\n",f[1][m].first+(f[1][m].second>0));
for(int i=1;i<=n;i++){
for(int j=1;j<=sz[i];j++)f[i][j]=pii(0,0);
}
}
}