1. 程式人生 > 其它 >洛谷P1174 打磚塊 | CCPC2021網路賽8.28 1011 動態規劃 分組揹包

洛谷P1174 打磚塊 | CCPC2021網路賽8.28 1011 動態規劃 分組揹包

喜提CCPC2021網路賽原題

題意相當於是要在每一列中選若干個磚塊打掉,消耗所需的子彈數並得到對應的得分。最大化k個子彈能得到的最大得分。

預處理出第\(i\)\(j\)個子彈能得到的最大得分,記為\(sum[i][j]\),那麼這可以轉為一個分組揹包問題。但一個小問題是,在沒有子彈時,你不能在打掉'Y'格,因此我們需要追蹤最後一發子彈的去向。

\(sum[i][j][0]\)​​表示第\(i\)​​列用\(j\)​​個子彈,全域性的最後一發子彈不打在這一列能得到的最大得分,\(sum[i][j][1]\)​​表示第\(i\)​​列用\(j\)​​​個子彈,全域性的最後一發子彈打在這一列的最大得分。

最後一發子彈沒有打在這一列的話,預處理時能從第\(n\)​​行不斷往上爬直到無法爬為止,遇到'Y'就能以0的代價拿下。但若最後一發子彈打在這一列,碰上'N'時,\(sum[i][j][1]\)​​需要用\(sum[i][j-1][0]\)​​​更新。據此可以寫出預處理程式碼如下:

for (int i = 1; i <= m; i++) {
   	for (int j = n, cnt = 0; j >= 1; j--) {
        if (c[j][i] == 'Y') {
            sum[i][cnt][0] += a[j][i];
        } else {
            cnt++;
            sum[i][cnt][0] = sum[i][cnt-1][0] + a[j][i];
            sum[i][cnt][1] = sum[i][cnt-1][0] + a[j][i];
        }
    } 
}

然後考慮修改後的“分組揹包”。記\(dp[i][j][0]\)​表示前\(i\)​列\(j\)​​發子彈,最後一發子彈不打在前i列能得到的最大得分,\(dp[i][j][1]\)​表示前\(i\)​列\(j\)​​發子彈,最後一發子彈打在前\(i\)​列能得到的最大得分。轉移有如下幾種:

1.最後一發打在當前列,即\(l>0\)

dp[i][j][1] = max(dp[i][j][1], dp[i-1][j-l][0]+sum[i][l][1]);

2.最後一發打在前i列,但不是當前列,即\(j-l>0\)

dp[i][j][1] = max(dp[i][j][1], dp[i-1][j-l][1]+sum[i][l][0]);

3.最後一發不打在前\(i\)​​列

dp[i][j][0] = max(dp[i][j][0], dp[i-1][j-l][0]+sum[i][l][0]);
for (int i = 1; i <= m; i++) {
	for (int j = 0; j <= k; j++) {	//一共有j發子彈
        for (int l = 0; l <= min(j, n); l++) {	//嘗試在這一列打l發
           	//case 3
            dp[i][j][0] = max(dp[i][j][0], dp[i-1][j-l][0]+sum[i][l][0]);
            if (l) {	//case 1
                dp[i][j][1] = max(dp[i][j][1], dp[i-1][j-l][0]+sum[i][l][1]);
            }
            if (j-l) {	//case 2
                dp[i][j][1] = max(dp[i][j][1], dp[i-1][j-l][1]+sum[i][l][0]);
            }
        }
    }
}

然後就可以過了。