题目描述
在一个圆形操场的四周摆放N堆石子,现要将石子有次序地合并成一堆.规定每次只能选相邻的2堆合并成新的一堆,并将新的一堆的石子数,记为该次合并的得分。
试设计出1个算法,计算出将N堆石子合并成1堆的最小得分和最大得分.
输入输出格式
输入格式:
数据的第1行试正整数N,1≤N≤100,表示有N堆石子.第2行有N个数,分别表示每堆石子的个数.
输出格式:
输出共2行,第1行为最小得分,第2行为最大得分.
思路:
本题要求了相邻两堆石子的合并,那么本题就是一道经典的区间类动态规划问题,
该题目的动态规划特征:求一个大区间的最优解,而大区间是由小区间合并而来,所以只要小区间为最优解,那么合并求解得到的大区间也必然为最优解。
那么可以得到动态规划方程(方程中的变量的意义可以参考代码理解):
表示区间[i,j]的最小合并代价,k表示区间[i,j]内的分割点,用于求dp[i][j]的最优解.
其中因为合并石子的时候,代价 = 两个区间各自的代价 + 这两个区间石子的总量(也就是合并后,i到j的石子的总量)
当i==j的时候,不存在合并代价,赋值为零.
求最大合并代价只需改变min为max.
上述思路可以直接求解线状石子合并类问题。
本题中由于石堆摆在了一个环形操场,那么是一个环状区间DP,采用的解法是破环为链,将环复制一份存到数组,进行DP,最后求出一个区间[i,j](长度为N)有最优解即可。
AC代码:
#include <iostream>
#include <cstdio>
using namespace std;
int Sum[201];
int Max[201][201] = {0};
int Min[201][201] = {0};
int Stone[201];
int N;
int main(void)
{
scanf("%d",&N);
Sum[0] = 0;
for(int i = 1;i <= N;i++)
{
scanf("%d",&Stone[i]);
Sum[i] = Stone[i]+Sum[i-1];
}
for(int i = 1;i <= N;i++)
{
Stone[i+N] = Stone[i];
Sum[i+N] = Sum[i+N-1]+Stone[i+N];
}
for(int p = 1;p < N;p++)
{
for(int i =1;i+p <= 2*N;i++)
{
int r = i+p; //分割点上界
for(int j = i;j < r;j++) //DP分割点
{
Max[i][r] = max(Max[i][r],Max[i][j]+Max[j+1][r] + Sum[r]-Sum[i-1]);
}
}
}
for(int p = 1;p < N;p++)
{
for(int i =1;i+p <= 2*N;i++)
{
int r = i+p; //分割点上界
Min[i][r] = 12180;
for(int j = i;j < r;j++) //DP分割点
{
Min[i][r] = min(Min[i][r],Min[i][j]+Min[j+1][r] + Sum[r]-Sum[i-1]);
}
}
}
int ans1 = 12180;
int ans2 = 0;
for(int i = 1;i <= N;i++)
{
ans2 = max(ans2,Max[i][i+N-1]);
}
for(int i = 1;i <= N;i++)
{
ans1 = min(ans1,Min[i][i+N-1]);
}
printf("%d\n",ans1);
printf("%d",ans2);
return 0;
}
区间DP套路
for SectonLength ... n
for StartPoint ... EndPoint
for BreakPoint in [StartPoint,EndPoint]
DP();