P1880 [NOI1995] 石子合并 (区间DP)

image

[NOI1995] 石子合并

题目描述

在一个圆形操场的四周摆放 \(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。\(f[i][j]和g[i][j]\)分别表示将\([i,j]\)区间的石子合并成一堆后的最大/最小得分。状态转移方程:\(f[i][j] = max(f[i][k] + f[k+1][j] + sum[i][j])\),sum是前缀和。g同理。
n的范围在100以内,\(O(n^3)\)可行。

代码

#include <bits/stdc++.h>
using namespace std;
int n, ansx, ansn, f[205][205], g[205][205], a[205], s[205];
int d(int i, int j) {return s[j] - s[i - 1];}
int main() {
	scanf("%d", &n);
	for (int i = 1; i <= n; i ++) {
		scanf("%d", &a[i]);
		a[i + n] = a[i];
	}
	for (int i = 1; i <= 2 * n; i ++) s[i] = s[i - 1] + a[i]; 
	for (int len = 2; len <= 2 * n; len ++) {
		for (int i = 1, j; (j = i + len - 1) <= 2 * n; i ++) {
			g[i][j] = 1e9;
			for (int k = i; k < j; k ++) {
				f[i][j] = max(f[i][j], f[i][k] + f[k + 1][j] + d(i, j));
				g[i][j] = min(g[i][j], g[i][k] + g[k + 1][j] + d(i, j));
			}
		}
	}
	ansn = 1e9;
	for (int i = 1; i <= n; i ++) {
		ansx = max(ansx, f[i][i + n - 1]);
		ansn = min(ansn, g[i][i + n - 1]);
	}
	printf("%d\n%d", ansn, ansx);
	return 0;
}

image

posted @ 2022-10-24 14:52  YHXo  阅读(63)  评论(0)    收藏  举报