HDU7110. Shooting Bricks题解 动态规划
HDU7110. Shooting Bricks
题意:
见原题。
分析:
为了方便叙述,我们将能得到额外子弹的砖块叫做“Y砖块”,不能得到额外子弹的砖块叫做“N砖块”。
首先,可以发现,在有子弹的情况下,射Y砖块相当于不消耗子弹,射N砖块需要消耗一个子弹。由此,我们可以得出一个结论:在能够射Y砖块的时候,先射Y砖块总是不劣于射N砖块。
所以对于可以射Y砖块的情形,我们不需要进行决策,无脑射Y砖块即可。对于不能射Y砖块的情形,我们就需要决策怎么射N砖块,使得分数尽量多。我们发现对于只射N砖块的情形,每一列的射法是相互独立的。只有遇到Y砖块的时候,需要先射Y砖块。加之,由于整道题目可以归结为对于每一列,决策射多少发子弹能使得分数尽量多的问题,故而我们不妨一列列考虑。
由于Y砖块的存在,射多少发子弹并不意味着消耗多少发子弹,消耗多少发子弹也不意味着需要多少发子弹,我们需要考虑会出现什么样的情况。仔细分析,假如我决定射\(x\)发子弹,则可以射掉\(x\)个方块,假如里面有\(y\)个方块是Y砖块,则我射掉这\(x\)个方块需要消耗\(x-y\)发子弹,如果这\(x\)发子弹的最后一发打的是Y砖块,则需要\(x-y+1\)发子弹,如果这\(x\)发子弹的最后一发打的是N砖块,则需要\(x-y\)发子弹。
接下来我们就要考虑如何设计状态。根据问题,很自然地,我们可能会想到令\(dp(i,j)\)为考虑前\(i\)列,消耗不超过\(j\)发子弹能获得的最大价值。但是这无法体现出需要多少子弹的问题,可以考虑再加一维需要多少子弹的维度,由于消耗不超过\(j\)发只可能出现需要不超过\(j\)发或需要不超过\(j+1\)发的情况。故而可以设\(dpn(i,j)\)为考虑前\(i\)列,消耗不超过\(j\)发子弹,需要不超过\(j\)发子弹能获得的最大价值;设\(dpy(i,j)\)为考虑前\(i\)列,消耗不超过\(j\)发子弹,需要不超过\(j+1\)发子弹能获得的最大价值。
状态设计完后,我们考虑如何转移。为了方便叙述,设\(dn(i,j)\)为第\(i\)列,消耗不超过\(j\)发子弹,需要不超过\(j\)发子弹能获得的最大价值,\(dy(i,j)\)为第\(i\)列,消耗不超过\(j\)发子弹,需要不超过\(j+1\)发子弹能获得的最大价值。
考虑从前\(i-1\)列,转移到前\(i\)列,枚举第\(i\)列消耗的子弹数\(l\),我们发现无非\(4\)种情况
其中考虑这\(4\)种情况,能分别转移到什么样的状态。
第一种,\(dpn(i-1,j-l)+dn(i,l)\),即前\(i-1\)列消耗不超过\(j-l\)发,需要不超过\(j-l\)发的最优解加上第\(i\)列,消耗不超过\(l\)发,需要不超过\(l\)发的最优解,合并起来就应该是前\(i\)列,消耗不超过\(j\)发,需要不超过\(j\)发,即转移到\(dpn(i,j)\)。
第二种,\(dpn(i-1,j-l)+dy(i,l)\),即前\(i-1\)列消耗不超过\(j-l\)发,需要不超过\(j-l\)发的最优解加上第\(i\)列,消耗不超过\(l\)发,需要不超过\(l+1\)发的最优解。
当\(l\neq j\)时,对于前\(i-1\)列最后一发射的是N砖块,第\(i\)列最后一发射的是Y砖块,由于先射Y砖块总是不劣,所以合并后最后一发射的是前\(i-1\)列的最后一发所射的N砖块。合并后总共消耗不超过\(j\)发,需要不超过\(j\)发,即转移到\(dpn(i,j)\)。
当\(l=j\)时,对于前\(i-1\)列啥也没射,第\(i\)列最后一发射的是Y砖块,此时总共消耗不超过\(j\)发,需要不超过\(j+1\)发,即转移到\(dpy(i,j)\)
第三种,\(dpy(i-1,j-l)+dn(i,l)\),即前\(i-1\)列消耗不超过\(j-l\)发,需要不超过\(j-l+1\)发的最优解加上第\(i\)列,消耗不超过\(l\)发,需要不超过\(l\)发的最优解。
当\(l\neq 0\)时,对于前\(i-1\)列最后一发射的是Y砖块,第\(i\)列最后一发射的是N砖块,由于先射Y砖块总是不劣,所以合并后最后一发射的是第\(i\)列的最后一发所射的N砖块。合并后总共消耗不超过\(j\)发,需要不超过\(j\)发,即转移到\(dpn(i,j)\)。
当\(l=0\)时,对于前\(i-1\)列最后一发射的是Y砖块,第\(i\)列啥也没射,此时总共消耗不超过\(j\)发,需要不超过\(j+1\)发,即转移到\(dpy(i,j)\)
第四种,\(dpy(i-1,j-l)+dy(i,l)\),即前\(i-1\)列消耗不超过\(j-l\)发,需要不超过\(j-l+1\)发的最优解加上第\(i\)列,消耗不超过\(l\)发,需要不超过\(l+1\)发的最优解,合并后消耗不超过\(j\)发,需要不超过\(j+1\)发,即转移到\(dpy(i,j)\)。
写到这,根据实际意义,我们又发现\(dpn(i,j)\leq dpy(i,j),dn(i,j)\leq dy(i,j)\)。即第二种大于等于第一种,第三种大于等于第一种,第四种大于等于前三种。
所以最终的转移方程可以写成
代码:
#include <algorithm>
#include <cstdio>
#include <cstring>
using namespace std;
const int maxn = 205;
int n, m, k;
int f[maxn][maxn];
bool c[maxn][maxn];
int dn[maxn][maxn], dy[maxn][maxn];
int dpn[maxn][maxn], dpy[maxn][maxn];
void solve() {
scanf("%d%d%d", &n, &m, &k);
memset(dn, 0, sizeof(dn));
memset(dy, 0, sizeof(dy));
memset(dpn, 0, sizeof(dpn));
memset(dpy, 0, sizeof(dpy));
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= m; j++) {
char ch;
scanf("%d %c", &f[i][j], &ch);
c[i][j] = (ch == 'Y' ? 1 : 0);
}
}
for (int i = 1; i <= m; i++) {
int tot = 0;
for (int j = n; j >= 1; j--) {
if (c[j][i]) {
dy[i][tot] += f[j][i];
} else {
tot++;
dn[i][tot] = dy[i][tot - 1] + f[j][i];
dy[i][tot] = dn[i][tot];
}
}
}
for (int i = 1; i <= m; i++) {
for (int j = 0; j <= k; j++) {
for (int l = 0; l <= min(n, j); l++) {
if (l > 0)
dpn[i][j] = max(dpn[i][j], dpy[i - 1][j - l] + dn[i][l]);
if (l < j)
dpn[i][j] = max(dpn[i][j], dpn[i - 1][j - l] + dy[i][l]);
dpy[i][j] = max(dpy[i][j], dpy[i - 1][j - l] + dy[i][l]);
}
}
}
printf("%d\n", dpn[m][k]);
}
int main() {
int T;
scanf("%d", &T);
while (T--) {
solve();
}
return 0;
}