P1437 [HNOI2004]敲砖块 一道十分难想的DP题

题意:题意很简单,有n层方块,每层逐次减一块,每一块有一定权值;

    想要敲掉某块,就要先敲掉他的基甸,然后给我们一个操作数m,

      询问m个操作最多能得多少分;

思路:在最开始得思考中,直接考虑每一层逐一dp下来,会发现,每一层所包含得数与相邻得数都会有重复(除第一层外)

    这句话什么意思呢?比如,想要敲掉第三层第一个数,就得先敲1 2  第二个数就得敲 2 3 

      那么有重复得数,我们就不好操作,无法列出转移方程

   无从下手,看了别人博客后,发现是将其360°左右翻转,然后就能列出式子来

      举个例子

  2 2 3 4  翻转过后就是  4 3 2 2 
  8 2 7                  7 2 8   
  2 3                      3 2
  49                        49

那么 现在得情况是 第二列得8需要敲掉2 2 第二列得2 需要敲掉3 2 第二列得7需要敲掉3 4
我们先来观察一下这个图,假如我们想要在第三列敲两个数,那么只能敲前两个,因为第三个需要以前面得为基准
  然后,想要敲这两个数,前面一列(也就是3 7这一列)就必须要有一个数或者一个数以上。
    为什么呢?因为第三列第二个数需要以第二列第一个数为奠基
      所以我们想要选择某一列的k个数的时候,肯定为前k个,于是我们可以预处理出前缀和,这是第一步
        然后,定义一个dp【i】【j】【k】数组
          表述前i列敲掉j块,并且在第i列敲掉k块时的最大权值
于是,我们按列从左往右依次遍历,for(int i=1;i<=n;i++)
  接下来就是遍历在当前列要枚举的总砖数 for(int j=0;j<=min(m,(i+1)*i/2);j++)
    再遍历当前列要选几块 for(int k=0;k<=min(i,j);k++)
      再遍历上一列选几块 for(int l=max(0,k-1);l<=i-1;l++)
然后就能得出所有最优情况
我们最后再来遍历找值即可
 1 #include<bits/stdc++.h>
 2 using namespace std;
 3 const int maxn=55;
 4 const int inf=0x3f3f3f3f;
 5 int n,m,f[maxn][maxn];
 6 int dp[maxn][maxn*maxn][maxn];
 7 int main()
 8 {
 9     scanf("%d%d",&n,&m);
10     for(int i=1;i<=n;i++)
11         for(int j=n;j>=i;j--){
12             scanf("%d",&f[i][j]);
13             f[i][j]+=f[i-1][j];
14         }
15 //    for(int i=1;i<=n;i++){
16 //        for(int j=i;j<=n;j++){
17 //            printf("%d ",f[i][j]);
18 //        }
19 //        printf("\n");
20 //    }
21     memset(dp,-inf,sizeof(dp));
22     dp[0][0][0]=0;
23     for(int i=1;i<=n;i++)
24         for(int j=0;j<=min(m,(i+1)*i/2);j++)//总砖数
25             for(int k=0;k<=min(i,j);k++)//当前列选几块
26                 for(int l=max(0,k-1);l<=i-1;l++)//上一列选几块
27                     dp[i][j][k]=max(dp[i][j][k],dp[i-1][j-k][l]+f[k][i]);
28     int ans=0;
29     for(int i=1;i<=n;i++)
30         for(int j=0;j<=i;j++)
31             ans=max(ans,dp[i][m][j]);
32     printf("%d\n",ans);
33     return 0;
34 }
View Code

 


    
posted @ 2020-04-03 23:44  古比  阅读(130)  评论(0编辑  收藏  举报