原題傳送門
題目描述
在一個圓形操場的四周擺放 \(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;
}