洛谷P3177 [HAOI2015]樹上染色 (樹上揹包)

鏈接[HAOI2015]樹上染色

題目描述

有一棵點數爲 n 的樹,樹邊有邊權。給你一個在 0∼n 之內的正整數 k ,你要在這棵樹中選擇 k 個點,將其染成黑色,並將其他 的 n−k 個點染成白色。將所有點染色後,你會獲得黑點兩兩之間的距離加上白點兩兩之間的距離的和的受益。問受益最大值是多少。

輸入格式

第一行包含兩個整數 n,k。

第二到 n 行每行三個正整數 fr,to,dis,表示該樹中存在一條長度爲 dis 的邊 (fr, to)。輸入保證所有點之間是聯通的。

輸出格式
輸出一個正整數,表示收益的最大值。

輸入輸出樣例

輸入 #1
3 1
1 2 1
1 3 2

輸出 #1
3

說明/提示

對於 100% 的數據,0≤n,k≤2000。

Solution

把路徑拆分成邊,記錄一下每條邊被經過的次數,求任意兩白點/兩黑點通過此條邊的貢獻。
令dp[i][j]爲以i爲根的子樹中選擇j個黑節點對答案的最大貢獻,然後做樹上揹包即可。
細節處理:
1.每條邊的貢獻必須是選擇k個黑節點時的貢獻,所以有一些非法情況需要排除,即當前子樹選擇x個黑節點,剩餘子樹不足k - x個黑節點的情況。
2.第二層for循環必須先把k == 0 的情況先處理,否則f[u][j]會在之前的轉移中被更新,我們所需要原狀態已經被更新掉了。

代碼

#include <bits/stdc++.h>
using namespace std;
#define RI register int
typedef long long ll;
const int SZ = 2000 + 10;
ll dp[SZ][SZ];
struct zt
{
	int v;
	int w;
	int nxt;
}line[SZ * 2];
int fist[SZ],temp,n,k,siz[SZ];

inline void build(int x,int y,int z)
{
	line[++ temp] = (zt){y,z,fist[x]};
	fist[x] = temp;
}

inline void dfs(int u,int far)
{
	siz[u] = 1;
	dp[u][0] = dp[u][1] = 0;
	for(RI i = fist[u];i;i = line[i].nxt)
	{
		int v = line[i].v;
		if(v == far) continue;
		dfs(v,u);
		siz[u] += siz[v];
		for(RI j = min(k,siz[u]);j >= 0;j --)
			for(RI p = 0;p <= min(siz[v],j);p ++)
			{
				if(dp[u][j - p] == -1) continue;
				ll tot = 1ll * line[i].w * p * (k - p) + 1ll * line[i].w * (siz[v] - p) * (n - k - siz[v] + p);
				dp[u][j] = max(dp[u][j],dp[u][j - p] + dp[v][p] + tot); 
			}
	}
}

int main()
{
	memset(dp,-1,sizeof(dp));
	scanf("%d%d",&n,&k);
	int a,b,c;
	for(RI i = 1;i < n;i ++)
	{
		scanf("%d%d%d",&a,&b,&c);
		build(a,b,c);
		build(b,a,c);
	}
	dfs(1,1);
	printf("%lld\n",dp[1][k]);
	return 0;
}

2020.4.29

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章