P1272 重建道路

P1272 重建道路

题目描述

- 一场可怕的地震后,人们用N个牲口棚(1≤N≤150,编号1..N)重建了农夫John的牧场。
由于人们没有时间建设多余的道路,所以现在从一个牲口棚到另一个牲口棚的道路是  惟一的。
因此,牧场运输系统可以被构建成一棵树。John想要知道另一次地震会造成多严重的破坏。
有些道路一旦被毁坏,就会使一棵含有P(1≤P≤N)个牲口棚的子树和剩余的牲口棚分离,John想知道这些道路的最小数目。

输入格式

- 第1行:2个整数,N和P
第2..N行:每行2个整数I和J,表示节点I是节点J的父节点。

输出格式

- 单独一行,包含一旦被破坏将分离出恰含P个节点的子树的道路的最小数目。

输入 #1

- 11 6
  1 2
  1 3
  1 4
  1 5
  2 6
  2 7
  2 8
  4 9
  4 10
  4 11

输出 #1

- 2

说明/提示

- 【样例解释】
如果道路1-4和1-5被破坏,含有节点(1,2,3,6,7,8)的子树将被分离出来

题意描述

  • 有一棵树
  • 删掉一些节点
  • 使剩下的节点仍为一棵树,并恰好等于P;
  • 求删的最小次数

主要思路:树形DP

  • 首先输入整棵多叉树
  • 然后将多叉树转为二叉树(方便计算)
  • 求删掉该节点后,会有多少节点挂掉
  • 最后将二叉树转成答案
  • 遍历二叉树 DP+记忆化 求解

如何 DP

  • 设 dp( i , j )为在节点i删j个节点的最小次数
  • 对于节点i,有删和不删两种情况
  • 如果删,则
	dp( i , j ) = dp( t [ i ].r , j - t [  i ].d ) + 1

意为: 在右子树上删去(要删的 j 减去已经删的 t [ i ].d )节点的最小次数加上本次删的次数 1

  • 如果删, 还有一种特殊情况
	if(m==n-p)	dp( i , j )=dp(t[ k ].l,t[ k ].d-p)+1;

意为: 若其他子树还未删任何节点,可以在删去的子树中删去(子树的节点减去要保留的节点),即等同于不删子树,而删去除该子树外的所有节点

  • 如果不删,则
	dp( i , j )=dp(t [ i ].l , k )+dp(t [ i ].r ,m-k )

意为: 节点i左子树删 k 个节点,右子树删 m-k 个节 点的最小次数和

  • 加上记忆化

t [ i ].r为右子树,t [ i ].l为左子树
t [ i ].d为删掉该节点后,会有多少节点挂掉。

传朕旨意,宣代码

#include <iostream>
#include <cstdlib>
#include <cstdio>
#include <cmath>
#include <string>
#include <cstring>
#include <algorithm>
using namespace std;//流线型头文件

const int INF=9999999;
struct tree{ int d,l,r; }t[301];
int f[301][301];
int n,p;//定义树

int dp(int k,int m)
{
	if(f[k][m]>0)		return f[k][m];
	if(m==0)		return 0;//删0个节点次数为0
	if(m<0)			return INF;//不可能删负数个节点
	if(k==0)		return INF;//不存在的节点
	int tmp;
	f[k][m]=INF;
	tmp=dp(t[k].r,m-t[k].d)+1;
	if(tmp<f[k][m])	f[k][m]=tmp;
	if(m==n-p)	tmp=dp(t[k].l,t[k].d-p)+1;
	if(tmp<f[k][m])	f[k][m]=tmp;
	int lv,rv;
	for(int i=0; i<=m; i++)
	{
		lv=dp(t[k].l,i);
		if(lv>=INF)	continue;
		rv=dp(t[k].r,m-i);
		if(rv>=INF)	continue;
		if(lv+rv<f[k][m])	f[k][m]=lv+rv;
	}
	return f[k][m];
}//树形DP+记忆化

void dfs(int x)
{
	if(x==0)	return;
	if(t[x].l==0)
	{
		t[x].d=1;
		return;
	}
	int r=t[x].l;
	t[x].d+=1;
	while(r>0)
	{
		dfs(r);
		t[x].d+=t[r].d;
		r=t[r].r;
	}
}//遍历树,求删掉该节点后,会有多少节点挂掉

int main()
{
	scanf("%d%d",&n,&p);
	int x,y,r;
	memset(t,0,sizeof(t));
	memset(f,-1,sizeof(f));
	for(int i=1; i<n; i++)
	{
		scanf("%d%d",&x,&y);
		if(t[x].l==0)	t[x].l=y;
		else
		{
			r=t[x].l;
			while(t[r].r>0)	r=t[r].r;
			t[r].r=y;
		}//转二叉树
	}
	dfs(1);
	cout<<dp(1,n-p)<<endl;
	return 0;
}
posted @ 2020-02-16 20:18  白菜道士  阅读(144)  评论(0编辑  收藏  举报