DP動態規劃-爬塔(雙層dp)

retrogogogo發表於2020-12-08

DP動態規劃-爬塔(雙層dp)

比賽來源:牛客 - 中國計量大學現代科技學院第四屆“中競杯”程式設計校賽
題目--------F題

description:

高川最喜歡的遊戲當屬 Slay the Spire,這是一款爬塔遊戲,你需要從一座塔的底部一直爬到頂部,在爬塔的過程中,塔的每一層都有許多的寶物等你來拿。
高川從塔的左側開始攀爬,從底部爬到頂部,再從右側從頂部逐步下到底部。塔總共有 n 層,每一層都有很多寶物從左到右排列。在左側攀爬時,他只能從每層的最左邊按順序取寶物,在右側下降時,他只能從每層的最右邊按順序取寶物。每個寶物都有一個價值,他最多拿 m 個寶物,他想知道自己從塔上下來時,最多可以拿的寶物價值和是多少

在這裡插入圖片描述

Sample:
在這裡插入圖片描述

解題思路:

  • 從題目中可以看出每一層都是獨立的,每一層中都有一個拿取寶物的順序限制,這也是題目的核心。
  • 所以首先,我們設定dpPerLine[i][j]來表示第i層選j個寶藏時,在這層的最大寶藏價值。可以用前字尾和來快速實現。
  • 然後,我們那拿到pPerLine後,可以直接去動態規劃總共選j個寶物時的最大價值dp[j],最後得到最後的解dp[m]。具體看程式碼註釋,這裡注意j和k要逆序遍歷,因為要保證每一層拿少數的狀態值不會去影響拿多數時的狀態值,不然則可能會出現連續拿一層同一個高價值寶藏的情況。具體情況可以自己去試一試。

原始碼與註釋:

#include<iostream>
#include<algorithm>
using namespace std;
int c[110][110];//寶藏權重
int pre[110][110], suf[110][110];//每一層的字首和、字尾和
int x[110];//每一層的寶藏數量
int dpPerLine[110][110];//第i層選j個寶藏時,在這層的最大寶藏價值。
int dp[10010];//拿i個寶藏的最大價值
int main()
{
	int n, m;//塔層數,寶藏可選數量
	cin >> n >> m;
	for (int i = 1; i <= n; i++) {
		cin >> x[i];
		for (int j = 1; j <= x[i]; j++) {
			cin >> c[i][j];
			pre[i][j] = pre[i][j - 1] + c[i][j];//計算字首和
		}
	}
	for (int i = 1; i <= n; i++) {
		for (int j = 1; j <=x[i]; j++)
			suf[i][j] = pre[i][x[i]] - pre[i][x[i]-j];//計算字尾和
	}
	//dp第i層選j個寶藏時,在這層的最大寶藏價值。
	for (int i = 1; i <= n; i++) {//第i層
		for (int j = 1; j <= x[i]; j++) {//在i層選j個寶藏
			for (int k = 0; k <= j; k++) {//此時方案為字首選k個
				dpPerLine[i][j] = max(dpPerLine[i][j], pre[i][k] + suf[i][j - k]);
			}
		}
	}
	//num動態表示此時最多可以拿到寶藏的數量
	int num = 0;	
	for (int i = 1; i <= n; i++) {//遍歷至i層(根據資料讀取時的順序)
		num += x[i];
		//此時一共選j個寶藏,
		//j一定小於 總共可選數量上限m 和 現在可選數量上限num
		for (int j = min(num, m); j >=1 ;j--) {
			//我們選擇在這一層選k個寶藏,遍歷k
			//k一定小於 本層最大寶藏數量x[i] 和 此時一共選的寶藏數j
			for (int k = min(x[i],j); k>=1 ; k--) {
				//此時共選j個數量,分別是來自這一層的k個和前面的j-k個
				dp[j] = max(dp[j] , dp[j - k] + dpPerLine[i][k]);
			}
		}
	}
	cout << dp[m] << endl;
	return 0;
}

純原始碼:

#include<iostream>
#include<algorithm>
using namespace std;
int pre[110][110],suf[110][110],c[110][110];
int x[110];
int dpPerLine[110][110],dp[10010];
int main()
{
	int n, m;
	cin >> n >> m;
	for (int i = 1; i <= n; i++) {
		cin >> x[i];
		for (int j = 1; j <= x[i]; j++) {
			cin >> c[i][j];
			pre[i][j] = pre[i][j - 1] + c[i][j];
		}
	}
	for (int i = 1; i <= n; i++)
		for (int j = 1; j <= x[i]; j++)
			suf[i][j] = pre[i][x[i]] - pre[i][x[i] - j];

	for (int i = 1; i <= n; i++)
		for (int j = 1; j <= x[i]; j++)
			for (int k = 0; k <= j; k++)
				dpPerLine[i][j] = max(dpPerLine[i][j], pre[i][k] + suf[i][j - k]);

	int num = 0;
	for (int i = 1; i <= n; i++) {
		num += x[i];
		for (int j = min(num, m); j >= 1; j--)
			for (int k = min(x[i], j); k >= 1; k--)
				dp[j] = max(dp[j], dp[j - k] + dpPerLine[i][k]);
	}
	cout << dp[m] << endl;
	return 0;
}

結果:

在這裡插入圖片描述

  可以看到這個方法速度還是比較快的,但和16+ms的大佬比還是比不了der,如果覺得這個方法太繁瑣可以去比賽裡面看看大佬們的程式碼,不過這個方法對於加深dp的理解還是有意義的。

相關文章