动态规划——洛谷1880石子合并
题目:
题目描述
在一个园形操场的四周摆放N堆石子,现要将石子有次序地合并成一堆.规定每次只能选相邻的2堆合并成新的一堆,并将新的一堆的石子数,记为该次合并的得分。
试设计出1个算法,计算出将N堆石子合并成1堆的最小得分和最大得分.
输入输出格式
输入格式:
数据的第1行试正整数N,1≤N≤100,表示有N堆石子.第2行有N个数,分别表示每堆石子的个数.
输出格式:
输出共2行,第1行为最小得分,第2行为最大得分.
输入输出样例
输入样例#1:
4
4 5 9 4
输出样例#1:
43
54
所属专栏:戳我访问
测评网址:戳我访问
这题与《信息学奥赛一本通》里的例题名字相同,但内容不同。
这题采用动态规划解决。
先来看看这题的简化版
一列石堆,每一堆由一些石子堆成。每一次合并相邻的两堆,代价为两堆石子的个数。最终要将所有的石子合并为一堆,总的代价就是每一次代价的和。
不同的合并方式代价是不同的,所以希望能够得到最小代价。
输入说明:
第一行一个数N
接下来N个数,表示一开始每堆石子的个数
输出说明:
一个数cost,表示最少的代价
输入样例:
8
5 3 4 1 3 2 3 4
输出:
74
数据范围: n<=3000
这题就和《信息学奥赛一本通》里面的“合并石子”一模一样了。
想想这题怎么做,直接分析:
这里分析的方法采用之前讲到的分析方法,如果你不知道这个分析方法,请访问:戳我访问
状态表达:f[i][j]表示在i到j的区间内,把这些石子都合成一堆,最小的代价。
状态转移:f[l][r] = std::min(f[l][r],f[l][k]+f[k+1][r]+num[r]-num[l-1]);
q其中l和r分别是i和j,num为前缀和。
下面的就不写了,不是很重要。
那么跳回到这个问题,这这个问题里面的石子是以一个全的形式排列的,那么,也就是说,这对石子的头和尾是相邻的,遇到这种问题,最好的办法就是把石子的长度变为2,把之前的的石子的数量拷贝一份放到尾部,我说的坑能有点不清楚,举个栗子:
1 2 3 4经过操作了以后就变成了1 2 3 4 1 2 3 4
代码:
#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<algorithm>
#include<cstring>
int num[1001],f[1001][1001],sum[1001];
int main()
{
int n;
std::cin>>n;
for(int i = 1;i<=n;i++)
{
std::cin>>num[i];
num[i+n] = num[i];
}
for(int i = 1;i<n*2;i++)num[i] += num[i-1];
for(int i = 2;i<=n;i++)
for(int l = 1;l<n*2-i+1;l++)
{
int r = l+i-1;
f[l][r] = 0x3f3f3f3f;
for(int k = l;k<r;k++)
f[l][r] = std::min(f[l][r],f[l][k]+f[k+1][r]+num[r]-num[l-1]);
}
int ans = 0x3f3f3f3f;
for(int i = 1;i<=n;i++)ans = std::min(f[i][i+n-1],ans);
std::cout<<ans<<std::endl;
for(int i = 2;i<=n;i++)
for(int l = 1;l<n*2-i+1;l++)
{
int r = l+i-1;
f[l][r] = 0;
for(int k = l;k<r;k++)
f[l][r] = std::max(f[l][r],f[l][k]+f[k+1][r]+num[r]-num[l-1]);
}
ans = -0x3f3f3f3f;
for(int i = 1;i<=n;i++)ans = std::max(f[i][i+n-1],ans);
std::cout<<ans;
return 0;
}
随时随刻,当你想要颓废的时候,想想之前为何而来,当你想要放弃的时候,想想之前你为何坐在这里