敲砖块 动态规划

敲砖块

注意:感谢Enquir大佬提供的hack数据,本题解仅提供正确思路,代码仅供参考,代码可被以下数据攻破
input:
2 1
2 1
2

output:1
answer:2

题目描述

在一个凹槽中放置了N层砖块,最上面的一层有N块砖,从上到下每层依次减少一块砖。每块砖都有一个分值,敲掉这块砖就能得到相应的分值,如图所示。
在这里插入图片描述
如果你想敲掉第i层的第j块砖的话,若i=1,你可以直接敲掉它,若i>1,则你必须先敲掉第i-1层的第j和第j+1块砖。

你现在可以敲掉最多M块砖,求得分最多能有多少。

输入格式

输入文件第一行有两个正整数N和M;

接下来的N行,描述这N层砖块上的分值A[i,j],满足0≤A[i,j]≤100。

输出格式

仅一行,包含一个整数,为最大的得分。

输入样例

4 5
2 2 3 4
8 2 7
2 3
49

输出样例

19

解题思路

题目大意:给你一个像上面这样的三角形,如果你想敲掉第i层的第j块砖的话,若i=1,你可以直接敲掉它,若i>1,则你必须先敲掉第i-1层的第j和第j+1块砖,求得分最多能有多少。


看到这个题目是不是有点感觉?本题与这一道动态规划新手必刷题有点相像,然而上洛谷查了一下,本题难度那一道题是,简直是天壤之别了。
好了,废话不多说,开始解题!


对于这种最值问题,脑海中第一个想法就是:动态规划

看着题目中的条件,一个隐隐约约的转移方程在脑海中浮现:
f [ k ] [ i ] [ j ] f[k][i][j] f[k][i][j]表示敲第 k k k块砖敲到 ( i , j ) (i,j) (i,j)的最大值,
f [ k ] [ i ] [ j ] = m a x ( f [ k ] [ i ] [ j ] , f [ k − 1 ] [ i − 1 ] [ j ] + f [ k − 1 ] [ i − 1 ] [ j + 1 ] ) f[k][i][j]=max(f[k][i][j],f[k-1][i-1][j]+f[k-1][i-1][j+1]) f[k][i][j]=max(f[k][i][j],f[k1][i1][j]+f[k1][i1][j+1])
老哥,能不能别这么冲动 很显然,我们可以得到一个错解,并且我们很快意识到,如果是以这种解法去做的话,是有后效性的,而动态规划是无后效性的,简单来说,我们不能让过去的决策影响未来的决策,不走回头路。

我们的思考陷入了僵局,不如换个角度思考问题,既然从上往下,从下往上推都不行,不妨试试从左往右推或者从右往左推
我们先来整理一下这个三角形:
三角形
紧接着,我们要在这个三角形上发掘一些有用的信息,如果我们要敲掉 ( i , j ) (i,j) (i,j),那么 ( i − 1 , j ) , ( i − 2 , j ) ⋯ ( 2 , j ) , ( 1 , j ) (i-1,j),(i-2,j)\cdots(2,j),(1,j) (i1,j),(i2,j)(2,j)(1,j)都是要敲掉的。
而且我们还要敲掉 ( i − 1 , j + 1 ) (i-1,j+1) (i1,j+1),但是敲掉 ( i − 1 , j + 1 ) (i-1,j+1) (i1,j+1)的办法不止一种,就像下面这张图:
在这里插入图片描述
在这张图中,我们目标敲掉 ( 2 , 2 ) (2,2) (2,2),绿色格子就是我们要敲掉同一列的格子,然后在红色箭头指向的两个格子中任意一个被敲掉, ( 2 , 2 ) (2,2) (2,2)都能敲掉。
斜上箭头指着的格子就是 ( i − 1 , j + 1 ) (i-1,j+1) (i1,j+1) ( 1 , 3 ) (1,3) (1,3)
下面那个格子 ( 2 , 3 ) (2,3) (2,3)如果被敲掉了,那么根据我们上面的规则, ( 1 , 3 ) (1,3) (1,3)一定会被敲掉,因为这两个格子在同一列
由此我们可以知道,对于目标 ( i , j ) (i,j) (i,j)要敲掉 ( i − 1 , j + 1 ) (i-1,j+1) (i1,j+1)可以去敲 ( i − 1 , j + 1 ) , ( i , j + 1 ) , ( i + 1 , j + 1 ) , ⋯   , ( n − ( i + 1 ) + 1 , j + 1 ) (i-1,j+1),(i,j+1),(i+1,j+1),\cdots,(n-(i+1)+1,j+1) (i1,j+1),(i,j+1),(i+1,j+1),,(n(i+1)+1,j+1)

好了,到这一步我们得出一个比较明显的结论:
f [ i ] [ j ] = m a x ( f [ i − 1 ] [ j + 1 ] , f [ i ] [ j + 1 ] , f [ i ] [ j + 1 ] , ⋯   , f [ n − i ] [ j + 1 ] ) + ∑ k = 1 i a [ k ] [ j ] f[i][j]=max(f[i-1][j+1],f[i][j+1],f[i][j+1],\cdots,f[n-i][j+1]) + \sum_{k=1}^{i}a[k][j] f[i][j]=max(f[i1][j+1],f[i][j+1],f[i][j+1],,f[ni][j+1])+k=1ia[k][j]

我们再将敲了第几个考虑进去, f [ k ] [ i ] [ j ] f[k][i][j] f[k][i][j]表示已经敲了 k k k个且第 k k k个敲了 ( i , j ) (i,j) (i,j)的最大的分情况,根据上面的规则,当前一步敲了 ( i , j ) , ( i − 1 , j ) , ( i − 2 , j ) ⋯ ( 2 , j ) , ( 1 , j ) (i,j),(i-1,j),(i-2,j)\cdots(2,j),(1,j) (i,j),(i1,j),(i2,j)(2,j)(1,j)一共 i i i块砖,可得式子:
f [ k ] [ i ] [ j ] = max ⁡ i − 1 ≤ l ≤ n − i ( f [ k − i ] [ l ] [ j + 1 ] ) + ∑ p = 1 i a [ p ] [ j ] f[k][i][j]=\max_{i-1\leq l \leq n-i}(f[k-i][l][j+1]) + \sum_{p=1}^{i}a[p][j] f[k][i][j]=i1lnimax(f[ki][l][j+1])+p=1ia[p][j]
我们的答案就是在所有的 f [ k ] [ i ] [ j ] f[k][i][j] f[k][i][j]里寻找的最大值了。


代码

#include<iostream>
#include<string>
#include<cstring>

using namespace std;
int n,m,ans;
int a[55][55],sum[55][55];
int f[55][55][555];

int main()
{
    
    
	cin>>n>>m;
	for(int i=1;i<=n;i++)
		for(int j=1;j<=n-i+1;j++)
			cin>>a[i][j];
	
	for(int i=1;i<=n;i++)
		for(int j=1;j<=n-i+1;j++)
			sum[j][i]=sum[j-1][i]+a[j][i];//每一列的前缀和 
			
	memset(f,-1,sizeof(f));
	f[0][n+1][0]=0;//初始化
	
	for(int i=n;i>=1;i--)//从最后一列开始 枚举i 
		for(int j=1;j<=n-i+1;j++)//从第一行开始 枚举j 
			for(int k=j;k<=m;k++)//枚举打掉(j,i)后一共已经打多少块砖 枚举k 
			{
    
    
				for(int p=j-1;p<=n-i;p++)//枚举p 上一步在p行 
					if(k-j>=0&&f[p][i+1][k-j]!=-1)//k-j可能为负数,万分小心!
					{
    
    
						f[j][i][k]=max(f[j][i][k],f[p][i+1][k-j]+sum[j][i]);
						ans=max(ans,f[j][i][k]);
					}
			}
	cout<<ans;
	return 0;
}

猜你喜欢

转载自blog.csdn.net/bell041030/article/details/89790142