【codevs1257】 打砖块

时间:2022-12-19 17:48:41

http://codevs.cn/problem/1257/ (题目链接)

题意

  在等腰三角形上打砖块,总共有m发炮弹,每块砖有一个权值,求打出的最大权值

Solution

  今天考试题,考场上的2个小时就此荒废了,1班是毛戏了。。看来dp还是有待提高,是时候多做做题了。

  此题一看就是dp对吧,就算不知道看了标签也知道了吧= =。我们把三角变成一个直角三角,于是我们发现,若要打第i列的第j个砖块,那么必须至少打掉第i+1列的前j-1个砖块以及第i列的前j-1个砖块,而打掉第i+1列前j-1个砖块就必须至少打掉第i+1列前j-2个砖块以及第i+2列前j-2个砖块……这样递归下去,形状大概长这样: 
  【codevs1257】 打砖块 
  比如说我们要打掉第1列第3行的4,我们就必须先打掉以(2,2)为顶点的三角形和一个矩形,如果有更多的子弹,那么就可以打掉以(2,3)为顶点的三角形中的一些点,前提是第2列从1到3都被打掉。也就是说要打掉第i列第j行的砖块,只与第i+1列的砖块有关。

  于是我们可以写出dp方程。用a[i][j]表示从第i列的第1行到第j行的权值和,用f[i][j][k]表示打掉第i列第j行的砖块,已经打掉了k个。所以方程:f[i][j][k]=max(f[i][j][k],f[i+1][p][k-j]+a[j][i]),p=[]。

  但是我们还要考虑一种情况,就拿上面那幅图来说吧,如果我们有4发子弹,那么答案是多少呢?打掉第一行的第1、2、4块和第二行第1块是最优的,答案为16。我们发现这个最优答案它并不是由上面我们所说的那样第i列第j行的答案只与后一列有关,它是间断的。 
  【codevs1257】 打砖块 
  怎么处理呢?我们可以再加入一行:第0行,第0行用来记录不取当前第i列中的砖块,直接取第i-1列中的砖块所得到的最大值,而这个并不与之前的dp方程矛盾,所以就做出来了。

代码

// codevs1257
#include<algorithm>
#include<iostream>
#include<cstdlib>
#include<cstring>
#include<cstdio>
#include<cmath>
#define LL long long
#define inf 2147483640
#define Pi acos(-1.0)
#define free(a) freopen(a".in","r",stdin),freopen(a".out","w",stdout);
using namespace std;
inline int getint() {
int f,x=0;char ch=getchar();
while (ch<'0' || ch>'9') {if (ch=='-') f=-1;else f=1;ch=getchar();}
while (ch>='0' && ch<='9') {x=x*10+ch-'0';ch=getchar();}
return f*x;
} const int maxn=100;
int n,m;
int a[maxn][maxn],f[maxn][maxn][600],t[maxn]; int main() {
scanf("%d%d",&n,&m);
for (int i=1;i<=n;i++)
for (int j=1;j<=n-i+1;j++) {scanf("%d",&a[i][j]);a[i][j]+=a[i-1][j];}
for (int i=1;i<=n;i++) t[i]=t[i-1]+i;
t[0]=1;int ans=0;
for (int i=n;i>=1;i--)
for (int j=0;j<=n-i+1;j++) {
for (int k=t[j];k<=m;k++)
for (int p=max(0,j-1);p<=n-i+1;p++)
f[i][j][k]=max(f[i][j][k],f[i+1][p][k-j]+a[j][i]);
ans=max(ans,f[i][j][m]);
}
printf("%d",ans);
return 0;
}