树上背包练习2

Luogu P1272 重建道路
P1272 重建道路

Solution

令dp[i][j]为以i为根的子树中保留j个节点,需要断开的最小边数,且当前子树不与父节点相连。

对于每个点的初始化dp[i][1],要断开与它相连的所有边,即它的度。
那么转移方程即为:

dp[u][j] = min(dp[u][j - k] + dp[v][k] - 2,dp[u][j])

方程中-2的原因是dp[v][k]和dp[u][j - k]分别把u->v的边断掉一次,现在要连接回来。
答案即为max(dp[i][p]),因为它已经成为一个独立的连通块。

代码

#include <bits/stdc++.h>
using namespace std;
const int SZ = 150 + 20;
const int INF = 0x3f3f3f3f;
typedef long long ll;
int fist[SZ],temp,n,p,d[SZ];
int dp[SZ][SZ];
struct zt
{
	int v,nxt;
}line[SZ << 1];

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

inline void dfs(int u,int far)
{
	dp[u][1] = d[u];
	for(int i = fist[u];i != -1;i = line[i].nxt)
	{
		int v = line[i].v;
		if(v == far) continue;
		dfs(v,u);
		for(int j = p;j >= 2;j --)
			for(int k = 1;k < j;k ++) //必须保留u才能连接它的子节点
				dp[u][j] = 	min(dp[u][j - k] + dp[v][k] - 2,dp[u][j]);
	}
} 
int main()
{
	memset(fist,-1,sizeof(fist));
	scanf("%d%d",&n,&p);
	int a,b;
	for(int i = 1;i < n;i ++)
	{
		scanf("%d%d",&a,&b);
		add(a,b);
		add(b,a);
		d[a] ++;
		d[b] ++;
	}
	memset(dp,0x3f,sizeof(dp));
	dfs(1,1);
	int ans = dp[0][0];
	for(int i = 1;i <= n;i ++) ans = min(ans,dp[i][p]);
	printf("%d\n",ans);
	return 0;
}

P3354 [IOI2005]Riv 河流
P3354 [IOI2005]Riv 河流

Solution

令dp[i][j][k][0]为以i为根的子树,j为i的祖先且j为伐木场,建k个伐木场的费用,且i处不为伐木场。
令dp[i][j][k][1]为以i为根的子树,j为i的祖先且j为伐木场,建k个伐木场的费用,且i处为伐木场。(i处的伐木场不包含在k中)

dp[u][s[j]][k][0] = min(dp[u][s[j]][k][0],dp[u][s[j]][k - x][0] + dp[v][s[j]][x][0]);
dp[u][s[j]][k][1] = min(dp[u][s[j]][k][1],dp[u][s[j]][k - x][1] + dp[v][u][x][0]);

对于答案我们只关心u和u的子树建立多少伐木场,不关心u处是否建了,所以把是否在i处建的两种情况合并。

代码

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int SZ = 100 + 20;
const int MAXN = 1000 + 100;
int fist[SZ],temp,dep[SZ],s[SZ],siz;
ll dp[SZ][SZ][52][2];
int n,m,w[SZ];
struct zt
{
	int v,nxt,w;
}line[MAXN];

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

inline void dfs(int u)
{
	s[++ siz] = u;
	for(int i = fist[u];i != -1;i = line[i].nxt)
	{
		int v = line[i].v;
		dep[v] = dep[u] + line[i].w;
		dfs(v);
		for(int j = 1;j <= siz;j ++)
			for(int k = m;k >= 0;k --)
			{
				dp[u][s[j]][k][0] += dp[v][s[j]][0][0];
				dp[u][s[j]][k][1] += dp[v][u][0][0];
				for(int x = 0;x <= k;x ++)
				{
					dp[u][s[j]][k][0] = min(dp[u][s[j]][k][0],dp[u][s[j]][k - x][0] + dp[v][s[j]][x][0]);
					dp[u][s[j]][k][1] = min(dp[u][s[j]][k][1],dp[u][s[j]][k - x][1] + dp[v][u][x][0]);
				}	
			}
	}
	for(int j = 1;j <= siz;j ++) 
		for(int k = 0;k <= m;k ++)
		{
			if(k >= 1)
				dp[u][s[j]][k][0] = min(dp[u][s[j]][k][0] + w[u] * (dep[u] - dep[s[j]]),dp[u][s[j]][k - 1][1]);
			else 
				dp[u][s[j]][k][0] += w[u] * (dep[u] - dep[s[j]]);
		}
	siz --;
}

int main()
{
	int vi,di;
	scanf("%d%d",&n,&m);
	memset(fist,-1,sizeof(fist));
	for(int i = 1;i <= n;i ++)
	{
		scanf("%d%d%d",&w[i],&vi,&di);
		add(vi,i,di);
	}	
	dfs(0);
	printf("%lld\n",dp[0][0][m][0]);
}

2020.7.14

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值