技能树上的dp问题

自古美人都是妖i 提交于 2019-11-29 11:08:53

Description

热爱电子娱乐的同学们对于技能树一定不陌生.就是说,要先学习低级的垃圾技能,特定
的几个垃圾技能学会了,才能学习更强的技能.比如说,要先学火球术和烈火墙,才能学习地狱
烈焰.科技树也是一样.要先研究出电力和内燃机,才能研究工业学.那么,现在我们把问题简化,
这是一个技能树(或者科技树).格子上的数,是威力值.要先学会第一排第二个和第三个,才能
学会第二排的第二个.每个技能学习的前提都是左上和右上的两个技能.假设现在有一个第一
层有N 个技能的技能树,而且技能点是有限的,只能学习M 个技能,我们想知道最大的威力值
之和是多少.
这里写图片描述
(ps:这个不是样例)

Input Format

第一行两个数N 和M,如题所述
之后N 行,第i 行,有n+1-i 个数.表示一个技能树.

Output Format

输出一个数,表示最大威力值之和

Sample Input

4 5
1 1 1 1
1 2 1
1 1
1

Sample Output

6

Data Limit

对于40%的数据,N<=10
对于100%的数据,N<=50,M<=500,所有数据都在int 之内.

技能树上的dp是一种不容易想到的dp问题,因为这种树状结构的dp大家都很熟悉,拿到这道题很容易就想成了树规或者从上向下,从下向上之类的dp问题,但是看到这道题的数据范围又是这么小,一看就有问题,然后就卡住了,最后只能写一个O(2n)的暴力。
那么现在我们仔细看看一下这道题,因为dp要无后效性(当前的状态只与前一个状态有关,而再前面的状态影响不了当前的状态,当前的状态也只能影响它后面的状态,不能影响再后面的状态),而如果我们从上向下dp的话,能不能去某一个数必须要看它上面的伞状结构的那一部分取没有,完全没法正常dp,方程一直列不出来。
我们就开始考虑,如何是我们的dp达到无后效性呢?
仔细观察我们可以发现无论我们怎么取,轮廓线都是一条锯齿状的折线。
这里写图片描述
我们发现每一列只有一个点在轮廓线上,而这些点,就是我们可以用来dp的点。
我们考虑从左向右dp,每一个在轮廓线上面的点只能有它前一列的上面或者下面得来,符合dp的无后效性
如上图,我们设“14”在第一行第一列,“33”在第二行第二列密码“15”在第一行第三列
而如果取一个点,也就说明要取它上面的所有点,我们可以先预处理将每个点上面点的数量存在num数组,价值和存在sum数组,f[k][i][j]表示[i][j]轮廓线取到这个坐标,一共取了k个数的最大值,然后开始状态转移!
f[k][i][j]=max(f[knum[i][j]][i1][j1],f[knum[i][j]][i+1][j1])+sum[i][j]

然后还有一个情况是需要注的,就是可能轮廓线不是连续的,前面一段,后面一段,这时我们要处理第0行的情况,第0行dp时状态就由它前面一个0行的和它上面前一个第1行dp过来,即:
f[k][0][j]=max(f[k][0][j2],f[k][1][j1])

大概就是这样了,然后我的程序在下面,但是,一直wa一个点,实在是改不出来了_(:зゝ∠),还是发上来作为参考吧。

#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
#define M 105
using namespace std;

int a[M][M],sum[M][M],num[M][M],f[M*5][M][M],n,m;

int main()
{
    cin>>n>>m;
    for(int i=1;i<=n;i++)
        for(int j=i;j<=2*n-i;j+=2)
            scanf("%d",&a[i][j]);
    for(int i=1;i<=n;i++)
        for(int j=1;j<=2*n-1;j++)
        {
            sum[i][j]=sum[i-2][j]+a[i][j];
            num[i][j]=num[i-1][j];
            if(a[i][j])num[i][j]++;
        }
    for(int i=1;i<=n*2-1;i+=2)
    {
        f[1][1][i]=a[1][i];
        f[1][0][i+1]=max(f[1][0][i-1],f[1][1][i]);
    }

    for(int k=2;k<=m;k++)
        for(int j=1;j<=2*n-1;j++)
            for(int i=(j%2==0?0:1);i<=n-abs(n-j);i+=2)
            {
                if(i==0)
                {
                    f[k][i][j]=max(f[k][0][j-2],f[k][1][j-1]);
                    continue;
                }
                if(k-num[i][j]<0||(f[k-num[i][j]][i-1][j-1]==0&&f[k-num[i][j]][i+1][j-1]==0))continue; 
                f[k][i][j]=max(f[k-num[i][j]][i-1][j-1],f[k-num[i][j]][i+1][j-1])+sum[i][j];
            }
    int ans=0;
    for(int i=1;i<=2*n-1;i++)
        ans=max(ans,f[m][1][i]);
    cout<<ans;
    return 0;
}    

如果有什么问题,或错误,请在评论区提出,谢谢。

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!