BZOJ 4987 (樹形DP)

###題面
https://www.lydsy.com/JudgeOnline/problem.php?id=4987
###分析
先考慮貪心,顯然k個節點形成一棵樹
求出樹的直徑,顯然直徑應該只被經過1次(最長的邊應該走最少次數),其他非直徑上的邊被經過2次
整體的形狀應該類似一條鏈上接着許多子樹
考慮樹形DP
子狀態:dp[x][i][j](j{0,1,2})dp[x][i][j]( j\in \left\{0,1,2\right\}),表示以x爲根的子樹中選了i個點,i個點中有j個直徑的端點時的長度之和

狀態轉移方程(類似揹包問題的轉移):
定義:x爲樹上一點,x爲y的父親,j爲x子樹去掉y子樹後子樹內選的點的個數,k爲y子樹內選的點的個數
dp[x][j+k][0]=min(dp[x][j+k][0],dp[x][j][0]+dp[y][k][0]+len2);dp[x][j+k][0]=min(dp[x][j+k][0],dp[x][j][0]+dp[y][k][0]+len*2);
x,y的子樹均不包含直徑,邊(x,y)屬於子樹內部的邊,被算了2次
dp[x][j+k][1]=min(dp[x][j+k][1],dp[x][j][0]+dp[y][k][1]+len);dp[x][j+k][1]=min(dp[x][j+k][1],dp[x][j][0]+dp[y][k][1]+len);
y的子樹包含直徑的1個端點,則直徑一定經過y,邊(x,y)在直徑上只被算1次
dp[x][j+k][1]=min(dp[x][j+k][1],dp[x][j][1]+dp[y][k][0]+len2);dp[x][j+k][1]=min(dp[x][j+k][1],dp[x][j][1]+dp[y][k][0]+len*2);
x的子樹(去掉y子樹,下同)包含直徑的1個端點,y的子樹不包含直徑的端點,通過邊(x,y)與直徑相連,被算2次
dp[x][j+k][2]=min(dp[x][j+k][2],dp[x][j][2]+dp[y][k][0]+len2);dp[x][j+k][2]=min(dp[x][j+k][2],dp[x][j][2]+dp[y][k][0]+len*2);
x的子樹包含直徑的2個端點,y的子樹不包含直徑的端點,通過邊(x,y)與直徑相連,被算2次
dp[x][j+k][2]=min(dp[x][j+k][2],dp[x][j][0]+dp[y][k][2]+len2);dp[x][j+k][2]=min(dp[x][j+k][2],dp[x][j][0]+dp[y][k][2]+len*2);
y的子樹包含直徑的2個端點,則直徑一定不經過邊(x,y),邊(x,y)被算2次
dp[x][j+k][2]=min(dp[x][j+k][2],dp[x][j][1]+dp[y][k][1]+len);dp[x][j+k][2]=min(dp[x][j+k][2],dp[x][j][1]+dp[y][k][1]+len);
x,y的子樹各包含直徑的1個端點,直徑一定過(x,y),邊(x,y)被算1次

###代碼

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define maxn 3005
#define INF 0x3f3f3f3f3f3f3f3f
using namespace std;
inline int qread(){
	int x=0,sign=1;
	char c=getchar();
	while(c<'0'||c>'9'){
		if(c=='-') sign=-1;
		c=getchar();
	}
	while(c>='0'&&c<='9'){
		x=x*10+c-'0';
		c=getchar(); 
	}
	return x*sign;
}

int n,m;
struct edge {
	int from;
	int to;
	int next;
	long long len;
	edge() {

	}
	edge(int u,int v,long long w,int hd) {
		from=u;
		to=v;
		next=hd;
		len=w;
	}
} E[maxn<<1];
int head[maxn];
int ecnt=0;
void add_edge(int u,int v,long long w) {
	E[++ecnt]=edge(u,v,w,head[u]);
	head[u]=ecnt;
}

int sz[maxn];
long long dp[maxn][maxn][3];
void dfs(int x,int fa) {
	sz[x]=1;
	dp[x][1][0]=dp[x][1][1]=0;
	for(int i=head[x]; i; i=E[i].next) {
		int y=E[i].to;
		long long len=E[i].len;
		if(y!=fa) {
			dfs(y,x);
			//j類似01揹包中的第二層,爲防止被算多次,倒序循環 
			for(int j=min(sz[x],m); j>=0; j--) {//j爲x子樹去掉y子樹後子樹內選的點的個數 
				for(int k=min(sz[y],m-j); k>=0; k--) {//k爲y子樹內選的點的個數 
					dp[x][j+k][0]=min(dp[x][j+k][0],dp[x][j][0]+dp[y][k][0]+len*2);
					//x,y的子樹均不包含直徑,邊(x,y)屬於子樹內部的邊,被算了2次
					dp[x][j+k][1]=min(dp[x][j+k][1],dp[x][j][0]+dp[y][k][1]+len);
					//y的子樹包含直徑的1個端點,則直徑一定經過y,邊(x,y)在直徑上只被算1次
					dp[x][j+k][1]=min(dp[x][j+k][1],dp[x][j][1]+dp[y][k][0]+len*2);
					//x的子樹包含直徑的1個端點,y的子樹不包含直徑的端點,通過邊(x,y)與直徑相連,被算2次
					dp[x][j+k][2]=min(dp[x][j+k][2],dp[x][j][2]+dp[y][k][0]+len*2);
					//x的子樹包含直徑的2個端點,y的子樹不包含直徑的端點,通過邊(x,y)與直徑相連,被算2次
					dp[x][j+k][2]=min(dp[x][j+k][2],dp[x][j][0]+dp[y][k][2]+len*2);
					//y的子樹包含直徑的2個端點,則直徑一定不經過邊(x,y),邊(x,y)被算2次
					dp[x][j+k][2]=min(dp[x][j+k][2],dp[x][j][1]+dp[y][k][1]+len);
					//x,y的子樹各包含直徑的1個端點,直徑一定過(x,y),邊(x,y)被算1次
				}
			}
			sz[x]+=sz[y];
		}
	}
}
int main() {
//	freopen("tree9.in","r",stdin); 
	int u,v;
	long long w;
	n=qread(); 
	m=qread();
	for(int i=1;i<n;i++){
		u=qread();
		v=qread();
		w=qread();
		add_edge(u,v,w);
		add_edge(v,u,w);
	}
	memset(dp,0x3f,sizeof(dp));
	dfs(1,0);
	long long ans=INF;
	for(int i=1;i<=n;i++){
		ans=min(ans,dp[i][m][2]);//根據之前貪心的分析,直徑一定經過這k個點中的一些點 
	}
	cout<<ans;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章