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\)种情况

\[dpn(i-1,j-l)+dn(i,l)\\ dpn(i-1,j-l)+dy(i,l)\\ dpy(i-1,j-l)+dn(i,l)\\ dpy(i-1,j-l)+dy(i,l) \]

其中考虑这\(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)\)。即第二种大于等于第一种,第三种大于等于第一种,第四种大于等于前三种。

所以最终的转移方程可以写成

\[dpy(i,j)=dpy(i-1,j-l)+dy(i,l)\\ dpn(i,j)=\max\limits_l\begin{cases} dpn(i-1,j-l)+dy(i,l),&l\neq j\\ dpy(i-1,j-l)+dn(i,l),&l\neq 0 \end{cases} \]

代码:

#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;
}
posted @ 2021-09-12 14:35  聆竹听风  阅读(89)  评论(0)    收藏  举报