2022.04.10 洛谷 P1272

24 篇文章 0 订阅
2 篇文章 0 订阅

[Problem] \color{blue}{\texttt{[Problem]}} [Problem]

  • 给定一棵有 n n n 个节点的有根树。
  • 每次操作可以删除树上两个点 ( u , v ) (u,v) (u,v) 间的连边。
  • 求最少需要几次操作可以从树上剥离出一个恰好有 P P P 个节点的子树。
  • 1 ≤ P ≤ n ≤ 150 1 \leq P \leq n \leq 150 1Pn150

[Analysis] \color{blue}{\texttt{[Analysis]}} [Analysis]

很明显应该是树上背包问题。

剥离节点可以有很多种方式:

  1. 直接删除 ( fa u , u ) (\text{fa}_{u},u) (fau,u) 间的连边,剩下一棵大小恰好为 P P P 的子树 u u u,操作次数为 1 1 1
  2. 删除 k k k 个节点 u 1 ⋯ k u_{1 \cdots k} u1k 的子树,剩下大小为 P P P 的残余子图。
  3. 先剥离出节点 u u u 的子树,再从这棵子树中删除一些节点,剩下大小为 P P P 的残余子图。

Situation 2,3 \text{Situation 2,3} Situation 2,3 很相似,可以用同一种方法解决。

f u , p f_{u,p} fu,p 表示从子树 u u u删除 p p p 个节点最少需要几次操作。

最后答案为:

min ⁡ { f 1 , sz ( 1 ) − m , min ⁡ i = 2 n { f i , sz ( i ) − m + 1 } } \min\left \{f_{1,\text{sz}(1)-m},\min\limits_{i=2}^{n} \left \{f_{i,\text{sz}(i)-m}+1 \right \} \right \} min{f1,sz(1)m,i=2minn{fi,sz(i)m+1}}

其中 sz ( i ) \text{sz}(i) sz(i) 表示以 i i i 为根的子树的大小。

树上背包求解即可。

[Solution] \color{blue}{\texttt{[Solution]}} [Solution]

树上背包,然后求解即可。

有一些需要注意的地方,具体看代码。

要特别注意那个分类讨论,不要漏讨论了某些情况。

[code] \color{blue}{\texttt{[code]}} [code]

const int inf=0x3f3f3f3f;

struct edge{
	int next,to;
}e[310];int h[160],te;
inline void add(int u,int v){
	e[++te]=(edge){h[u],v};h[u]=te;
}

int sz[160],a[160],f[160][160],n,m;

void dfs_init(int u,int Fa){
	for(int i=h[u];i;i=e[i].next){
		int v=e[i].to;
		if (v==Fa) continue;
		dfs_init(v,u);
		sz[u]+=sz[v];
	}
	sz[u]++;
}

int dp(int u,int P,int Fa){
	if (P==0) return f[u][P]=0;
	if (P==sz[u]) return f[u][P]=1;
	if (f[u][P]!=-1) return f[u][P];
	
	int tmp[160];//临时数组 
	bool BroTher=false;
	
	for(int i=0;i<=P;i++) tmp[i]=inf;
	
	for(int i=h[u];i;i=e[i].next){
		int v=e[i].to;
		if (v==Fa) continue;
		
		if (!BroTher){
			for(int j=0;j<=min(P,sz[v]);j++)
				tmp[j]=dp(v,j,u);
		}
		else{
			for(int k=P;k>=0;k--)
				for(int j=min(k,sz[v]);j>=0;j--)
					tmp[k]=min(tmp[k],tmp[k-j]+dp(v,j,u));
		}
		
		BroTher=true;
	}
	
	for(int i=0;i<=P;i++)
		f[u][i]=tmp[i];
	
	return tmp[P];
}

int main(){
	scanf("%d%d",&n,&m);
	for(int i=1,u,v;i<n;i++){
		scanf("%d%d",&u,&v);
		add(u,v);add(v,u);
	}
	
	dfs_init(1,0);
	
	
	memset(f,-1,sizeof(f));
	
	for(int i=0;i<=sz[1];i++) dp(1,i,0);
	
	int ans=f[1][sz[1]-m];
	for(int i=2;i<=n;i++)
		if (sz[i]>=m) ans=min(ans,f[i][sz[i]-m]+1);
	
	printf("%d",ans);
	
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值