【區間dp】石子合併

shimingxin1007發表於2024-06-10

原題傳送門

題目描述

在一個圓形操場的四周擺放 \(N\) 堆石子,現要將石子有次序地合併成一堆,規定每次只能選相鄰的 \(2\) 堆合併成新的一堆,並將新的一堆的石子數,記為該次合併的得分。

試設計出一個演算法,計算出將 \(N\) 堆石子合併成 \(1\) 堆的最小得分和最大得分。

輸入格式

資料的第 \(1\) 行是正整數 \(N\),表示有 \(N\) 堆石子。

\(2\) 行有 \(N\) 個整數,第 \(i\) 個整數 \(a_i\) 表示第 \(i\) 堆石子的個數。

輸出格式

輸出共 \(2\) 行,第 \(1\) 行為最小得分,第 \(2\) 行為最大得分。

樣例 #1

樣例輸入 #1

4
4 5 9 4

樣例輸出 #1

43
54

提示

\(1\leq N\leq 100\)\(0\leq a_i\leq 20\)


思路分析

我們可以使用區間 dp 來解決這道題。我們可以設 \(dp_{l,r}\) 表示合併區間 \((l,r)\) 的石子成一堆所得到的最小得分或最大得分,每一堆肯定是由兩堆合併而成的,而這兩堆可能是哪些呢?我們可以列舉是哪兩堆,列舉中間結點 \(k\),將 \((l,r)\) 區間分割成 \((l,k)\)\((k+1,r)\) 兩個子區間,這樣我們便得到了一個動態轉移方程:

\(dp_{l,r}=\min(dp_{l,r},dp_{l,k}+dp_{k+1,r}+sum_{l,r});\)

\(dp_{l,r}=\max(dp_{l,r},dp_{l,k}+dp_{k+1,r}+sum_{l,r});\)

其中,\(sum_{l,r}\) 的意思是區間 \((l,r)\) 的和,表示本次合併的分數,加入動規陣列中,\(dp_{l,k}+dp_{k+1,r}\) 則表示兩個子區間的最小或最大得分,最後對兩個區間 dp 陣列求 \(\min\)\(\max\) 即可。

注意,因為操場是圓形的,我們需要在 \(a\) 陣列後再增加一個 \(a\),以方便計算,模擬圓。

具體思路詳見註釋。

\(\texttt{code}\)

/*Written by smx*/
#include<bits/stdc++.h>
using namespace std;
int n;
int a[205],dpmin[205][205],dpmax[205][205],sum[205];
int main(){
	//freopen(".in","r",stdin);
	//freopen(".out","w",stdout);
	ios::sync_with_stdio(false);
	cin.tie(0);
	cout.tie(0);
	memset(dpmin,0x3f,sizeof(dpmin));//求最小,記得賦最大值
	cin>>n;
	for(int i=1;i<=n;i++){
		cin>>a[i];
		dpmin[i][i]=0;
		sum[i]=sum[i-1]+a[i];//字首和
	}
	for(int i=1;i<=n;i++){//增加一倍 a
		dpmin[i+n][i+n]=0;
		sum[i+n]=sum[i+n-1]+a[i];
	}
	for(int len=2;len<=n;len++){//區間長度
		for(int l=1;l+len-1<=2*n;l++){//列舉左端點
			int r=l+len-1;//找到右端點
			for(int k=l;k<r;k++){//列舉中間結點
				dpmin[l][r]=min(dpmin[l][r],dpmin[l][k]+dpmin[k+1][r]+sum[r]-sum[l-1]);
				dpmax[l][r]=max(dpmax[l][r],dpmax[l][k]+dpmax[k+1][r]+sum[r]-sum[l-1]);	//如上動態轉移方程
			}
		}
	}
	int ans1=1e9,ans2=0;
	for(int i=1;i<=n;i++){
		ans1=min(ans1,dpmin[i][i+n-1]);
		ans2=max(ans2,dpmax[i][i+n-1]);
	}//每個區間的結果取 min 或 max
	cout<<ans1<<"\n"<<ans2;
	return 0;
}

相關文章