石子合并
P1880 [NOI1995] 石子合并 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
区间dp模板题
题目描述
在一个圆形操场的四周摆放 N 堆石子,现要将石子有次序地合并成一堆.规定每次只能选相邻的2堆合并成新的一堆,并将新的一堆的石子数,记为该次合并的得分。
试设计出一个算法,计算出将 N 堆石子合并成 1 堆的最小得分和最大得分。
输入格式
数据的第 1 行是正整数 N,表示有 N 堆石子。
第 22 行有 NN 个整数,第 i 个整数 ai 表示第 i 堆石子的个数。
输出格式
输出共 2 行,第 1 行为最小得分,第 2 行为最大得分。
那么很显然,这是一个区间dp的问题
既然是区间dp,那么肯定要定义数组(水字数
因为这很显然是一个环,普通的存储方式肯定覆盖不了所有情况(例:4 3 2三堆合并)
所以这就要用到区间dp(不止)一个很重要(划掉)的思想——破环为链
具体来说就是把这个数组在原数组后re存储一遍
num[i+n]=num[i];
这样就可以实现从后往前进行dp
解决了存储问题,那么如何求每次合并石子的得分呢
显然有一种方法就是把每次的得分num[i]都加起来
但这样很显然时间复杂度很大,而且写起来很麻烦(
那么我们可以用前缀和相减来快速求这个区间内的得分
inline int d(int i,int j){return s[j]-s[i-1];}
下一步就是求dp方程了
定义一个p为合并的石子堆数,i为起始位置,j为终止位置
设置一个k为断点,代表在从i起的第k堆石子为分界点,合并这两堆石子,将k从i到j循环一遍,这样就可以求出合并这堆石子的最小(最大)值了
f1[i][j] = max(f1[i][j], f1[i][k]+f1[k+1][j]+d(i,j));
f2[i][j] = min(f2[i][j], f2[i][k]+f2[k+1][j]+d(i,j));
最后将合并n堆石子的得分从1到2*n循环一遍,就可以求出最小(最大)值了
那么下面是完整代码
#include<bits/stdc++.h>
using namespace std;
const int N=1e11;
int n,minx,maxx,f1[300][300],f2[300][300],num[300];
int s[300];
inline int d(int i,int j){return s[j]-s[i-1];}
int main()
{
scanf("%d",&n);
for(int i=1;i<=2*n;i++)
{
scanf("%d",&num[i]);
num[i+n]=num[i];
s[i]=s[i-1]+num[i];
}
for(int p=1;p<n;p++)
{
for(int i=1,j=i+p;(j<2*n) && (i<2*n);i++,j=i+p)
{
f2[i][j]=N;
for(int k=i;k<j;k++)
{
f1[i][j] = max(f1[i][j], f1[i][k]+f1[k+1][j]+d(i,j));
f2[i][j] = min(f2[i][j], f2[i][k]+f2[k+1][j]+d(i,j));
}
}
}
minx=N;
for(int i=1;i<=n;i++)
{
maxx=max(maxx,f1[i][i+n-1]);
minx=min(minx,f2[i][i+n-1]);
}
printf("%d\n%d",minx,maxx);
return 0;
}
封面是本人的洛谷头像(