poj 1947 树形DP+01背包

题大意是:给你一棵节点为n的树,问至少砍几刀可以孤立出一棵节点为m的子树。

d[i][j]为以i为根节点孤立出节点为j的子树至少需要砍得次数(注意:这个子树是包括根节点i的)。

接下来再说说怎么得到状态转移方程:

(1)j1的话,d[i][1]=节点i孩子的个数;

(2)若以i的孩子son[i]为根的子树不在以i为根节点孤立出节点数为j的子树的内部,则有没有以son[i]为根的子树无所谓;若以son[i]为根节点的子树有k个节点在以i为根孤立出节点数为j的子树中则 整个树分成两部分,此时,d[i][j]=d[i][j-k]+d[son[i]][k]-1;之所以减一是因为两个分开的子树重组在一起需要减去是他们分开的那一刀;总之我是这样想的也不知对也不对....

所以总的来说:d[i][j+k]=min{d[i][j]+d[son[i]][k]-1d[i][j+k]};

#include<iostream>
#include<cstring>
#include<vector>
using namespace std;
#define MAX_INT 123456
#define min(a,b) a<b ? a: b
vector <int> map[155];
int d[155][155],father[155],n,m;
int dfs(int root)
{
	int i,j,k;
	for(i=0;i<map[root].size();i++)
		dfs(map[root][i]);
	d[root][1]=map[root].size();
	for(i=0;i<map[root].size();i++)
		for(j=m-1;j>0;j--)//这里j需要从大往小和01背包类似
			if(d[root][j]!=MAX_INT)
		    	for(k=1;k<=m-j;k++)
					if(d[map[root][i]][k]!=MAX_INT)
			    	    d[root][j+k]=min(d[root][j]+d[map[root][i]][k]-1,d[root][j+k]);
	return d[root][m];
}
int main()
{
	int i,j,s,t,root,min1;
	while(cin>>n>>m)
	{
		memset(father,0,sizeof(father));
		for(i=1;i<=n;i++)
			for(j=1;j<=m;j++)
				d[i][j]=MAX_INT;
		for(i=0;i<n-1;i++)
		{
			cin>>s>>t;
			map[s].push_back(t);
			father[t]=s;
		}
		root=1;
		while(father[root]!=0)
			root=father[root];
		min1=dfs(root);
		/*for(i=1;i<=n;i++)
		{
			for(j=1;j<=m;j++)
				cout<<d[i][j]<<"  ";
			cout<<endl;
		}*/
		for(i=1;i<=n;i++)
            if(i!=root && d[i][m]<min1) min1=d[i][m]+1;//之所以加1是需要将以i为根的子树与整棵树分离
		cout<<min1<<endl;
		for(i=0;i<=n;i++)
			map[i].clear();
	}
	return 0;
}

posted @ 2011-07-18 17:01  书山有路,学海无涯  阅读(1579)  评论(1编辑  收藏  举报