[題解]P5858 Golden Sword

Sinktank發表於2024-03-26

P5858 「SWTR-3」Golden Sword

第一道自己想出遞推公式並且成功\(AC\)\(dp\)綠題。

題意簡述

\(n\)種原料,每個原料有一個耐久度\(a[i]\),必須按照\(1,2,…,n\)的順序放入鍊金鍋。但是鍊金鍋的容量是有限的,只有\(w\),所以在每次放入原料之前,都可以選擇取出\(0\sim s\)個原料再放當前的原料。

放入第\(i\)個原料後,所鍛造的武器的總耐久度就會增加\(len*a[i]\)\(len\)表示當前鍊金鍋中原料個數)。

詢問最大的“武器總耐久度”。

資料範圍:

輸入輸出格式

輸入:\(n,w,s\),而後\(n\)個整數\(a_1,a_2,…,a_n\)

輸出:一行,表示最終答案。

思路簡述

用下面的樣例進行演示:

7 4 2
-5 3 -1 -4 7 -6 5

輸出:17

如圖,用\(f\)作為\(dp\)陣列。\(f[i][j]\)表示放了前\(i\)個,放完後鍋中還有\(j\)個的最大值。

遞推公式:\(f[i][j]=a[i]*j+max(f[i-1][k])(j-1\leq k\leq min(w,j+s-1))\)

例如下圖求\(f[6][2]\),就是用\(max(綠色部分)+2倍的紫色部分\)

怎麼推導呢?我們發現\(dp[6][2]\)肯定可以由\(dp[5][1]\)推來的,因為\(f[5][1]\)的基礎上,不拿走任何元素就直接放入第\(6\)個元素,狀態就是\(f[6][2]\)

那麼\(dp[6][2]\)還能由哪些狀態推來呢?別忘了每放一個原料前,我們可以拿出最多\(s=2\)個元素。所以\(f[6][2]\)自然也能透過\(f[5][2]\)(拿\(1\)個元素)、\(f[5][3]\)(拿\(2\)個元素)。

這樣我們可以寫出程式碼(見Code部分#1)。

最佳化

然而這份程式碼提交上去,能\(A\)大部分,但還有\(TLE\)的點。這是因為我們每求一次最大值都要遍歷上一行。怎麼解決這個問題呢?沒錯,這就是「單調佇列最佳化\(dp\)

單調佇列我先開個坑,到時候填。

使用\(maxx[j]\)表示當前行即\(f[i]\)中,以\(j\)結尾,往前長度為\(min(j,s+1)\)的最大值。每次求完這一行的\(f\),就處理出\(maxx\)陣列供下一行使用。

這樣求\(f[i][j]\)時,就用\(maxx[j+s-1]\)就好了。

此時我們發現\(f\)也可以最佳化空間為一維(與HDU1024 Max Sum Plus Plus有異曲同工之妙,無論是在推導過程還是最佳化方面),這樣既最佳化了時間,還最佳化了空間,一舉兩得。

注意遞推完,下一行最多用到\(max(i+s,w+s)\),所以維護\(maxx\)時只需要迴圈至\(max(i+s,w+s)\)即可。

時間複雜度\(O(nw)\),程式碼見#2。

Code

#1
#include<bits/stdc++.h>
#define int long long
using namespace std;
int s,w,n,a[5010];
int f[5010][5010];
signed main(){
	cin>>n>>w>>s;
	for(int i=1;i<=n;i++) cin>>a[i];
	memset(f,0x80,sizeof f);//long long極小值
	for(int i=0;i<=w;i++) f[0][i]=0;
	for(int i=1;i<=n;i++){
		for(int j=1;j<=w;j++){
			if(j>i) break;
			for(int k=j-1;k<j+s;k++){
				if(k>w) break;
				f[i][j]=max(f[i][j],f[i-1][k]);
			}
			f[i][j]+=a[i]*j;
			cout<<f[i][j]<<" ";
		}
		cout<<endl;
	}
	int ans=LLONG_MIN;
	for(int i=1;i<=w;i++) ans=max(ans,f[n][i]);
	cout<<ans;
	return 0;
}
#2
#include<bits/stdc++.h>
#define int long long
using namespace std;
int s,w,n,a[5010],maxx[5010];
int f[5010];
deque<int> q;
signed main(){
	cin>>n>>w>>s;
	for(int i=1;i<=n;i++) cin>>a[i];
	for(int i=1;i<=n;i++){
		for(int j=1;j<=w;j++){
			if(j>i) break;
			f[j]=maxx[j+s-1]+a[i]*j;
		}
		q.clear();
		for(int j=1;j<=w+s;j++){
			if(j>i+s) break;
			if(j<=i&&j<=w){//如果i個元素加完了,就只出不入
				while(!q.empty()&&f[j]>=f[q.front()]) q.pop_back();
				q.push_back(j);
			}
			if(q.front()+s+1<=j) q.pop_front();
			maxx[j]=f[q.front()];
		}
	}
	int ans=LLONG_MIN;
	for(int i=1;i<=w;i++) ans=max(ans,f[i]);
	cout<<ans;
	return 0;
}

相關文章