1. 程式人生 > >NUIST OJ 1411 繼續合併石子 [DP]

NUIST OJ 1411 繼續合併石子 [DP]

題目

題目描述

在一個圓環上分佈著N堆石子,現要將石子有次序地合併成一堆.規定每次只能選相鄰的2堆合併成新的一堆,並將新的一堆的石子數記為得分。

輸入描述

每組資料的第1行為正整數N,1≤N≤100,表示有N堆石子.第2行有N個數,分別表示每堆石子的個數.

輸出描述

輸出共2行,第1行為最小得分,第2行為最大得分

樣例輸入

4
4 4 5 9

樣例輸出

43
54

題目分析

此題和我的上一篇題解十分相近,只需要做出一點微小的改動即可。
因為是環狀,所以可以首尾合併了。那麼為了簡單起見,用空間換寫起來方便【大霧】,直接將資料往後擴充套件了一倍,這樣就和上一題一模一樣了
唯一需要注意的是:答案不再儲存在dp[1][n],因為考慮到首尾合併的問題,實際上是向後滑動了的。因此,最小代價/得分為 min{dp[1][n],dp[2][n+1],……dp[n][2n-1]}。最大代價/得分同理求得。

本題的區間DP

用dp[i][j]表示合併[i,j]區間所需要的最小代價
那麼可以方便的得到如下關係
若i=j,則dp[i][j]=0
否則,則dp[i][j]=min{dp[i][k]+dp[k+1][j]}+sum[i][j]
發現:計算dp[i][j]的大區間,需要先計算出dp[i][j]的小區間,所以可以考慮設定一個變數len,從小往大遞增,根據此來控制先算小區間,再算大區間
最後,本題要求的是合併所有石子,即[i,j]區間和並的最小代價。
因此,本題答案儲存在dp[1][n]內

整體程式碼與執行結果

/*
[email protected]
*/
#include<iostream>
#include<algorithm>
#include<cstring>
#include<queue>
#define maxn 105
using namespace std;
int a[maxn*2];
int dp[maxn*2][maxn*2];
int dpm[maxn * 2][maxn * 2];
int sum[maxn*2][maxn*2];
int main() {
    int n;
    int
ma, mi; while (cin >> n) { memset(dp, 0, sizeof(dp)); memset(dpm, 0, sizeof(dpm)); for (int i = 1; i <= n; ++i) { cin >> a[i]; a[n + i] = a[i]; } for (int i = 1; i <= n; ++i) { sum[i][i] = a[i]; sum[n + i][n + i] = a[i]; for (int j = i+1; j <= 2*n; ++j) { sum[i][j] = sum[i][j - 1] + a[j]; sum[n + i][n + j] = sum[n + i][n + j - 1] + a[j]; } } for (int len = 2; len <= n; ++len) { for (int i = 1; i <= 2 * n - len + 1; ++i) { dp[i][i + len - 1] = dp[i][i] + dp[i + 1][i + len - 1] + sum[i][i + len - 1]; dpm[i][i + len - 1] = dpm[i][i] + dpm[i + 1][i + len - 1] + sum[i][i + len - 1]; for (int k = i + 1; k < i + len - 1; ++k) { dp[i][i + len - 1] = min(dp[i][i + len - 1], dp[i][k] + dp[k + 1][i + len - 1] + sum[i][i + len - 1]); dpm[i][i + len - 1] = max(dpm[i][i + len - 1], dpm[i][k] + dpm[k + 1][i + len - 1] + sum[i][i + len - 1]); } } } mi = dp[1][n]; ma = dpm[1][n]; for (int i = 2; i < n; ++i) { mi = min(mi, dp[i][i + n - 1]); ma = max(ma, dpm[i][i + n - 1]); } cout << ma << " " << mi << endl; } return 0; }

結果如下
這裡寫圖片描述