[ural 1018]Binary Apple Tree[树DP]

题意:

有棵苹果树,根节点编号为1.边权为该边上苹果数. 问保留x条树枝时, 能够保留的最多苹果数.

思路:

树DP.

令dp[ i ][ j ] 表示以 i 为根的子树保留 j 条树枝(包括 i 与其父节点相连的那一条在内)最多有多少苹果.

枚举关于子树的不同分配方案, 选择最大的.

树DP的特殊之处就在于它的第一维的变化是沿着dfs的方向走的.

#include <cstdio>
#include <cstring>
const int MAXN = 111;
int n, m, f[MAXN][MAXN], data[MAXN][MAXN];
bool vis[MAXN];
struct Tree
{
	int l, r, v;
	inline Tree() { l = r = v = 0; }
}a[MAXN];///a是森林...

inline void build(const int &x)///递归也可以inline啊...这样就不会爆栈了么?
{///const的引用,所以也可以传一个常量进去.为了省内存?
	vis[x] = true;
	for (int i = 1; i <= n; i++)
		if (!vis[i] && data[x][i])
		{
			if (!a[x].l)
				a[x].l = i;
			else//说了是二叉
				a[x].r = i;
			a[i].v = data[x][i];///将边权下落到远端点权
			build(i);
		}
}

inline int dfs(const int &x, const int &left)
{
	if (!x || !left)//没有名额了或者没有儿子了
		return 0;
	if (f[x][left])///记忆化搜索
		return f[x][left];///往下细分会有重叠
	int maxn = 0;
	for (int i = 0; i < left; i++)///实际保留的条数要小1
	{
		int l = dfs(a[x].l, i), r = dfs(a[x].r, left - i - 1);
		if (maxn < l + r)
			maxn = l + r;
	}///返回的时候,加上了自己.其实一共也就是这一条语句执行了"加"的操作.这是合理的.
	f[x][left] = maxn + a[x].v;///记忆化搜索
	return f[x][left];
}

int main()
{
	while (scanf("%d%d", &n, &m) != EOF)
	{
		memset(data, 0, sizeof(data));
		memset(vis, false, sizeof(vis));
		memset(f, 0, sizeof(f));
		for (int i = 1; i <= n; i++)
			a[i] = Tree();///分配给a一个个元素(这句多余不?)
		for (int i = 1; i < n; i++)
		{
			int tmp1, tmp2, tmpv;
			scanf("%d%d%d", &tmp1, &tmp2, &tmpv);
			data[tmp1][tmp2] = data[tmp2][tmp1] = tmpv;///邻接矩阵
		}
		build(1);
		int ans = dfs(1, m + 1);
		///留下m条树枝,但是有一条是连着自己这个节点的
		///这是在dfs的过程中一直遵循的,否则无法分呐.
		///只能讲通向儿子节点的边划到儿子树中,否则统计的时候就会混乱
		printf("%d\n", ans);
	}
	return 0;
}
自己敲一遍:

#include <cstdio>
#include <vector>
using namespace std;
const int MAXN = 105;
int dp[MAXN][MAXN],n,m;
typedef struct node
{
    int v,w;
    node(){}
    node(int _v, int _w):v(_v),w(_w){}
}node;
typedef struct tree
{
    int l,r,w;
    tree(){}
}tree;
vector<node> edge[MAXN];
tree a[MAXN];
bool vis[MAXN];

inline void build(const int& x)
{
    vis[x] = true;
    for(int i=0;i<edge[x].size();i++)
    {
        int y = edge[x][i].v;
        if(!vis[y])
        {
            if(!a[x].l)
                a[x].l = y;
            else
                a[x].r = y;
            a[y].w = edge[x][i].w;
            build(y);
        }
    }
}

inline int dfs(const int& i, const int& j)
{
    if(!i || !j)    return 0;
    if(dp[i][j])   return dp[i][j];
    int max = 0;
    for(int k=0;k<j;k++)
    {
        int l = dfs(a[i].l, k), r = dfs(a[i].r, j-1-k);
        if(max<l+r)
            max = l+r;
    }
    dp[i][j] = max + a[i].w;
    return dp[i][j];
}

int main()
{
    scanf("%d %d",&n,&m);
    for(int i=1,u,v,w;i<n;i++)
    {
        scanf("%d %d %d",&u,&v,&w);
        edge[u].push_back(node(v,w));
        edge[v].push_back(node(u,w));
    }
    build(1);
    int ans = dfs(1, m+1);
    printf("%d\n",ans);
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值