偷天换日(树形动态规划)

偷天换日

Luogu P3360

题目背景

神偷对艺术馆内的名画垂涎欲滴准备大捞一把。

题目描述

艺术馆由若干个展览厅和若干条走廊组成。每一条走廊的尽头不是通向一个展览厅,就

是分为两个走廊。每个展览厅内都有若干幅画,每副画都有一个价值。经过走廊和偷画都是

要耗费时间的。

警察会在n 秒后到达进口,在不被逮捕的情况下你最多能得到的价值。

这里写图片描述

输入输出格式

输入格式:
第一行一个整数 n(n≤600)。

第二行若干组整数,对于每组整数(t,x),t 表示进入这个展览厅或经过走廊要耗费 t

秒的时间,若x>0 表示走廊通向的展览厅内有x 幅画,接下来

x对整数(w,c)表示偷一幅价值为 w 的画需要 c秒的时间。若

x=0 表示走廊一分为二。(t,c≤5; x≤30)

输入是按深度优先给出的。房间和走廊数不超过 300 个。

输出格式:
仅一个整数,表示能获得的最大价值。

输入输出样例

输入样例#1:
50
5 0 10 1 10 1 5 0 10 2 500 1 1000 2 18 1 1000000 4
输出样例#1:
1500
说明

来源:改编

思路

这个题目根据题意我们可以把这个图实质上转换为一棵二叉树,而且每一个节点的值取决于它所有的儿子节点的值,而且状态可转移,没有后效性,所以我们这个题就是一个树形动态规划。
我这个写法和网上大多数的题解不同,我的算法思路不是很正常,所以希望大家尽量理解。
首先很自然的思路就是我们将输入数据转化为一棵树。这棵树上记录的是每个节点上下的连线或者是每个节点拓展出来的一堆名画。
需要注意的是,对于路权我们要乘以2,因为要来回两次。仍然需要我们注意的是,总时间要减去1,因为如果大盗和警察在同一时间碰面,还是会被抓。
由于这棵树的某些节点对应的可能是一堆名画,然后我们对于每一幅名画都只有取与不取两个状态,所以我们要在这棵树的末端做一个比较经典01背包问题。
接下来就是01的状态转移方程,这里的dp[x][t]表示x节点下t时间内能够得到的最大价值,应该由t减去某一个名画的时间转移而来。

dp[x][t]=max(dp[x][t],dp[x][t-paint[x][i][1]]+paint[x][i][0]);

然后就是返回到一个节点后的整合,同样也是一个动态规划,状态转移方程如下:

for(int t=n;t>=0;t–)
for(int k=t-a[p].w;k>=0;k–)
dp[x][t]=max(dp[son][k]+dp[x][t-a[p].w-k],dp[x][t]);

代码

#include<iostream>
#include<cstring>
#include<cstdio>
#include<cstdlib>
#include<algorithm>
#include<cmath>
using namespace std;

int i,j,m,n,temp;
int hd[601],paint[601][31][2];
int dp[601][601];
struct data
{
    int v,w,nxt;
}a[20001];

int r()
{
    int ans=0,f=1;
    char ch;
    while(ch<'0'||ch>'9')
    {
        if(ch=='-')
        {
            f=-1;
        }
        ch=getchar();
    }
    while(ch>='0'&&ch<='9')
    {
        ans*=10;
        ans+=ch-'0';
        ch=getchar();
    }
    return ans*f;
}

void add(int x,int y,int z)
{
    a[++temp].v=y;
    a[temp].nxt=hd[x];
    a[temp].w=z;
    hd[x]=temp;
}

void build(int x,int fa)
{
    int xx=r()*2;
    add(fa,x,xx);

    int ord=r();
    if(ord==0)
    {
        build(x<<1,x);
        build(x<<1|1,x);
    }
    else
    {
        paint[x][0][0]=ord;
        for(int i=1;i<=paint[x][0][0];i++)
        {
             paint[x][i][0]=r();
             paint[x][i][1]=r();
        }
    }
}

void dfs(int x)
{
    int p,son;
    if(paint[x][0][0])
    {

        for(int i=1;i<=paint[x][0][0];i++)
        for(int t=n;t>=paint[x][i][1];t--)
        {
            dp[x][t]=max(dp[x][t],dp[x][t-paint[x][i][1]]+paint[x][i][0]);
        }
    }

    for(p=hd[x];p;p=a[p].nxt)
    {
        son=a[p].v;
        dfs(son);
        for(int t=n;t>=0;t--)
        for(int k=t-a[p].w;k>=0;k--)
        {
            dp[x][t]=max(dp[son][k]+dp[x][t-a[p].w-k],dp[x][t]);
        }
    }
}

int main()
{
    n=r();
    n--;
    build(1,0);

    dfs(0);
    cout<<dp[0][n];
    return 0;
}
/*
50
5
0
10
1
10
1
5
0
10
2
500
1
1000
2
18
1
1000
4

20
5 0 1 6 0 5 4 0 5 6 3 2 3 2
2 5 5 2 1 2 5 6

20
5 0 1 3 2 0 8 2 4 2 3 1 5 1

20
5 0 3 0 1 2 1 2 5 3 2 1 2 5 3 1 1 2

*/

这里写图片描述

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值