餐馆 树型动态规划模板题

餐馆
Description
K妹的胡椒粉大卖,这辣味让食客们感到刺激,许多餐馆也买这位K妹的账。
有N家餐馆,有N-1条道路,这N家餐馆能相互到达。K妹从1号餐馆开始。每一个单位时间,K妹可以在所在餐馆卖完尽量多的胡椒粉,或者移动到有道路直接相连的隔壁餐馆。第i家餐馆最多需要A[i]瓶胡椒粉。K妹有M个单位的时间,问她最多能卖多少胡椒粉。
Input
第一行有两个正整数N,M。
第二行描述餐馆对胡椒粉的最大需求量,有N个正整数,表示A[i]。
接下来有N-1行描述道路的情况,每行两个正整数u、v,描述这条道路连接的两个餐馆。
Output
一个整数,表示她最多能卖的胡椒粉瓶数。
Sample Input
样例1输入
3 5
9 2 5
1 2
1 3
样例2输入
4 5
1 1 1 2
1 2
2 3
3 4
样例3输入
5 10
1 3 5 2 4
5 2
3 1
2 3
4 2
Sample Output
样例1输出
14
样例2输出
3
样例3输出
15
Data Constraint
对于10%的数据,N≤20。
对于50%的数据,N≤110。
对于100%的数据1 ≤ N, M ≤ 500,1 ≤ A[i]≤ 10^6。
Hint
在样例1的中,辣妹到达城市2后就恰好没时间卖辣椒粉了。

分析题目,通过观察我们可以发现,这是一个简单的树型动规。
题目大意是,有N个餐馆,N-1条道路使它们相连。因此,我们可以确定,这是一个无环图。
观察题目数据范围,节点个数N≤500,时间M≤500,我们就可以很容易的确定一个状态f[i][j]表示搜索到节点i时,第j秒钟所能得到的最大分数为f[i][j]。
但是,我们会发现,由于这道题目的图是一棵树,K妹从u向其中一条边(u,v1)走到v1去卖胡椒粉,所以其想要去v2卖胡椒粉,并且需要经过边(u,v2),必然要重新经过边(u,v1)回到u点,再从u点走(u,v2)到v2卖胡椒粉。
所以,我们不选择使用f[i][j]记录到点i,时间为j时所得到的分数。
那么,换一种思维,我们可以用f[i][t][k]来记录状态,表示从第i个点开始,花费了t秒时间,在节点i及其子树所能得到的最大分数,并且k=0时状态表示得到分数并且没有回到节点i,k=1表示得到f[i][t][k]的分数并且在t秒回到了节点i。
那么,我们就可以得到状态转移方程。
对于一个节点i本身,在其时间t内,它是否选择在自己节点售卖胡椒粉,转移方程为
f[i][t][k]=max(f[i][t][k],f[i][t-1][k])
对于一个节点i,在其时间t时,对于其任意的子节点j,在子节点j花费的时间t2(t2<t),有状态转移方程
f[i][t][0]=max(f[i][t][0],f[i][t-t2-1][1]+f[j][t2][0],f[i][t-t2-2][0]+f[j][t2][1])
f[i][t][1]=max(f[i][t][1],f[i][t-t2-2][1]+f[j][t2][1])
那么,我们在遍历所有点后,就可以直接寻找i=1时所有的f[i][t][k],记录下最大值ans,输出即可。

代码如下

#include <bits/stdc++.h>

#define LL long long int

using namespace std;

int n,m,tot;
LL ans;
LL f[510][510][2];
int q[510][510];
int tail[510];
int h[510],A[510];
bool bo[510];
struct node{
    int to,next;
}v[1010];

void find(int );
void add(int ,int );

int main()
{
    scanf("%d%d",&n,&m);
    for (int i=1;i<=n;i++)
        scanf("%d",&A[i]);
    for (int i=1;i<n;i++)
    {
        int a,b;
        scanf("%d%d",&a,&b);
        add(a,b);
    }
    find(1);
    printf("%lld",ans);
    return 0;
}

void find(int u)
{
    bo[u]=true;
    for (int jump=h[u];jump;jump=v[jump].next)
    {
        int w=v[jump].to;
        if (!bo[w])
        {
            q[u][++tail[u]]=w;
            find(w);
        }
    }
    for (int i=1;i<=tail[u];i++)
    {
        int w=q[u][i];
        for(int tu=m;tu;tu--)
            for (int tw=1;tw<tu;tw++)
            {
                f[u][tu][0]=max(f[u][tu][0],(f[u][tu-tw-1][1]+f[w][tw][0]));
                if (tu>=tw+2)
                {
                    f[u][tu][0]=max(f[u][tu][0],(f[u][tu-tw-2][0]+f[w][tw][1]));
                    f[u][tu][1]=max(f[u][tu][1],(f[u][tu-tw-2][1]+f[w][tw][1]));
                }
            }
    }
    for (int i=m;i;i--)
    {
        f[u][i][0]=max(f[u][i][0],f[u][i-1][0]+A[u]);
        f[u][i][1]=max(f[u][i][1],f[u][i-1][1]+A[u]);
        if (u==1)
            ans=max(ans,max(f[u][i][0],f[u][i][1]));
    }
    return ;
}

void add(int a,int b)
{
    v[++tot].to=b;
    v[tot].next=h[a];
    h[a]=tot;
    v[++tot].to=a;
    v[tot].next=h[b];
    h[b]=tot;
    return ;
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值