codeforces 1280D

題目鏈接

題意

給一棵樹,樹上每個點有兩個權值,分別是wi,bi
要求將樹分成m個聯通塊,要求最多能有幾個聯通塊內的wi>bi\sum wi \gt \sum bi

數據範圍

多組數據,組數<=100
n,m<=3000
n1e5\sum n\le 1e5

解法

樹形揹包

首先有一個簡單的想法:f[i][j][k]表示第i個點的子樹,被分成了j個聯通塊,其中有k個聯通塊滿足要求,此時與i相連的聯通塊的權值最大是多少.
用子樹揹包的方式轉移可以做到O(n3)O(n^3)

然後考慮如果同一棵子樹,劃分聯通塊的數量一樣,那麼如果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);
		}
	}
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章