1. 程式人生 > 實用技巧 >[P1880] [NOI1995]石子合併(環形dp)

[P1880] [NOI1995]石子合併(環形dp)

題目描述

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

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

輸入格式

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

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

輸出格式

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

輸入輸出樣例

輸入 #1

4
4 5 9 4

輸出 #1

43
54

說明/提示

\(1≤N≤100,0≤ai≤200。\)

【思路】

拆環,把鏈的長度延長到\(2n\), 區間dp做法,最後列舉左端點取其中最大/最小的結果。

#include <algorithm>
#include <cmath>
#include <cstdio>
#include <cstring>
#include <list>
#include <map>
#include <iostream>
#include <iomanip>
#include <queue>
#include <set>
#include <stack>
#include <string>
#include <unordered_map>
#include <vector>
#define LL long long
#define inf 0x3f3f3f3f
#define INF 0x3f3f3f3f3f3f
#define PI 3.1415926535898
#define F first
#define S second
#define endl '\n'
#define lson  rt << 1
#define rson  rt << 1 | 1
#define f(x, y, z) for (LL x = (y), __ = (z); x < __; ++x)
#define _rep(i, a, b) for (LL i = (a); i <= (b); ++i)
using namespace std;

const int maxn = 207;
const int maxm = 2e4 + 7;
const int mod = 19650827;
int n;
int a[maxn], dpmx[maxn][maxn], dpmn[maxn][maxn], sum[maxn];

int main()
{
	ios::sync_with_stdio(false);
	cin.tie(0);
	cin >> n;
	memset(dpmx, 0, sizeof(dpmx));
	memset(dpmn, inf, sizeof(dpmn));
	_rep(i, 1, n)
	{
		cin >> a[i];
		a[i + n] = a[i];
	}
	_rep(i, 1, 2 * n)
	{
		sum[i] = sum[i - 1] + a[i];
		dpmn[i][i] = 0;
	}
	_rep(len, 1, n)
	{
		_rep(l, 1, n * 2 - len)
		{
			int r = l + len;
			_rep(k, l, r - 1)
			{
				dpmx[l][r] = max(dpmx[l][r], dpmx[l][k] + dpmx[k + 1][r] + sum[r] - sum[l - 1]);
				dpmn[l][r] = min(dpmn[l][r], dpmn[l][k] + dpmn[k + 1][r] + sum[r] - sum[l - 1]);
			}
		}
	}
	int mx = 0, mn = inf;
	_rep(i, 1, n)
	{
		mx = max(mx, dpmx[i][i + n - 1]);
		mn = min(mn, dpmn[i][i + n - 1]);
	}
	cout << mn << endl << mx << endl;
}