题意:
给出一颗有 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][i−k]+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][i−k]+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×p≤108,而 d p [ u ] [ i ] dp[u][i] dp[u][i]的 i ≤ p i\leq p i≤p, 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;
}