洛谷U53878 树上背包,指针分配内存

题意:

给出一颗有 n n n个结点的树,求最少删去几条边,可以使得图有一个大小为 p p p的连通分量?

Solution:

d p [ u ] [ i ] dp[u][i] dp[u][i]为子树保留 i i i个点,最少的子树删边数是多少?这显然是个树上背包,转移如下

d p [ u ] [ i ] = m i n ( d p [ u ] [ i − k ] + d p [ v ] [ k ] ) dp[u][i]=min(dp[u][i-k]+dp[v][k]) dp[u][i]=min(dp[u][ik]+dp[v][k])

诚然,子树若一个点都不保留,这棵子树的删边数应该是1,即删去根和根的父亲所连接的,我们需要在子树之间转移,所以 d p [ u ] [ 0 ] dp[u][0] dp[u][0]这一项应该是不存在的一项,但使子树一个都不保留是对根的父节点来说可实现的,于是 d p [ u ] [ i ] dp[u][i] dp[u][i]还需要考虑当前子树不保留的情况,即总转移方程为

d p [ u ] [ i ] = m i n ( m i n ( d p [ u ] [ i − k ] + d p [ v ] [ k ] ) , d p [ u ] [ i ] + 1 ) dp[u][i]=min(min(dp[u][i-k]+dp[v][k]),dp[u][i]+1) dp[u][i]=min(min(dp[u][ik]+dp[v][k]),dp[u][i]+1)

初始化条件,当我们尚未为当前子树合并任何子树时,应当只存在1个结点,并且此时操作次数为0,于是 d p [ u ] [ 1 ] = 0 dp[u][1]=0 dp[u][1]=0,其余的置为无穷大

一开始做的时候状态不太明确,将 d p [ u ] [ 0 ] dp[u][0] dp[u][0]置为了1,实际上状态更准确的说法应该是子树至少与父亲相连,并且保留 i i i个点,最少的删边数为何?整颗子树删去我们只需要在父亲转移的时候特殊考虑即可,正是因为这样, d p [ u ] [ 0 ] = i n f dp[u][0]=inf dp[u][0]=inf的含义为不可实现,其余 i n f inf inf的含义应当是等待更新的一个极大值

加强版由于空间太大,但 n × p ≤ 1 0 8 n\times p\leq 10^8 n×p108,而 d p [ u ] [ i ] dp[u][i] dp[u][i] i ≤ p i\leq p ip n n n个点,那么总空间最多 n × p n\times p n×p,于是预先计算字树大小,分配一个 m a x ( p , s u m [ u ] ) max(p,sum[u]) max(p,sum[u])给子树 d p dp dp即可

#include<bits/stdc++.h>
#include<iostream>
#include<queue>
#include<cstdio>
#include<bitset>
#include<cstring>
#include<map>
#include<algorithm>
using namespace std;

using ll=long long;
const int N=100005,inf=0x3fffffff;
const long long INF=0x3f3f3f3f3f3f,mod=998244353;

struct way
{
	int to,next;
}edge[N<<1];
int cnt,head[N];

void add(int u,int v)
{
	edge[++cnt].to=v;
	edge[cnt].next=head[u];
	head[u]=cnt;
}

vector<int>f(100000005,inf);
int n,p,sum[N],*dp[N],*pp=&f[0];

void dfs1(int u,int fa)
{
	sum[u]=1;
	for(int i=head[u];i;i=edge[i].next)
	{
		int v=edge[i].to;
		if(v==fa) continue;
		dfs1(v,u);
		sum[u]+=sum[v];
	}
	dp[u]=pp; pp+=max(p,sum[u]);
}

void dfs(int u,int fa)
{
	sum[u]=1; dp[u][1]=0;
	for(int i=head[u];i;i=edge[i].next)
	{
		int v=edge[i].to;
		if(v==fa) continue;
		dfs(v,u);
		sum[u]+=sum[v];
		for(int j=min(sum[u],p);j>=1;j--)
		{
			int min1=dp[u][j]+1;
			for(int k=1;k<=min(sum[v],j);k++) min1=min(min1,dp[u][j-k]+dp[v][k]);
			dp[u][j]=min1;
		}
	}
}

int main()
{
	ios::sync_with_stdio(false);
	cin>>n>>p;
	for(int i=1;i<n;i++)
	{
		int u,v; cin>>u>>v;
		add(u,v); add(v,u); 
	}
	dfs1(1,0); dfs(1,0);
	int ans=inf;
	for(int i=1;i<=n;i++) ans=min(ans,dp[i][p]+(i!=1));
	cout<<ans;
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值