洛谷P1880 [NOI1995]石子合并 (区间DP+前缀和预处理)
题目描述
在一个圆形操场的四周摆放 NNN 堆石子,现要将石子有次序地合并成一堆.规定每次只能选相邻的2堆合并成新的一堆,并将新的一堆的石子数,记为该次合并的得分。
试设计出一个算法,计算出将 NNN 堆石子合并成 111 堆的最小得分和最大得分。
输入格式
数据的第 111 行是正整数 NNN,表示有 NNN 堆石子。
第 222 行有 NNN 个整数,第 iii 个整数 aia_iai 表示第 iii 堆石子的个数。
输出格式
输出共 222 行,第 111 行为最小得分,第 222 行为最大得分。
输入输出样例
输入 #1
4 4 5 9 4
输出 #1
43 54
说明/提示
1≤N≤1001\leq N\leq 1001≤N≤100,0≤ai≤200\leq a_i\leq 200≤ai≤20。
非常经典的一道例题...
从题面可以看出,线性DP难以处理(想了半天不知道怎么转移....所以只能用区间DP,这里可以看出,最终的石子堆一定是由两个子堆合并而来,即两个长度较小的区间的信息转移到了长度更长的区间,划分点k是转移的决策(蓝书原话)。这个题用递归+记忆化搜索或者直接循环都能写,我还是喜欢递归写法...由分析可以写出转移方程:dp[l][r]=dp[l][i]+dp[i+1][r]+a[l...r],其中dp[l][r]表示l...r合并而来的石子堆的最小花费(最大花费同理)。a[l...r]可以通过前缀和O(1)查询。这里还有一个问题就是这是一个环形的圈,对于环形的序列有一个很常用的处理方法就是复制一遍原序列并接在其后面,这样只需要1~n,2~n+1...枚举一遍即可。
记得dp[i][i]初始化为0.
#include <bits/stdc++.h> using namespace std; int n,a[205]; int sum[205]={0}; int dp1[205][205];//最小 int dp2[205][205];//最大 int process1(int l,int r) { if(dp1[l][r]!=0x3f3f3f3f)return dp1[l][r]; int i; for(i=l;i<r;i++) { dp1[l][r]=min(dp1[l][r],process1(l,i)+process1(i+1,r)+sum[i]-sum[l]+a[l]+sum[r]-sum[i+1]+a[i+1]); } return dp1[l][r]; } int process2(int l,int r) { if(dp2[l][r]!=0)return dp2[l][r]; int i; for(i=l;i<r;i++) { dp2[l][r]=max(dp2[l][r],process2(l,i)+process2(i+1,r)+sum[i]-sum[l]+a[l]+sum[r]-sum[i+1]+a[i+1]); } return dp2[l][r]; } int main() { cin>>n; int i; memset(dp1,0x3f3f3f3f,sizeof(dp1)); memset(dp2,0,sizeof(dp2)); for(i=1;i<=n;i++) { scanf("%d",&a[i]); a[n+i]=a[i]; } for(i=1;i<=2*n;i++) { sum[i]=sum[i-1]+a[i];//环形前缀和 } for(i=1;i<=2*n;i++) { dp1[i][i]=0;//初始化为0 最开始的一小堆并非移动而得来 dp2[i][i]=0; } int mmin=0x3f3f3f3f,mmax=0; for(i=1;i<=n;i++)//枚举环形 { mmin=min(mmin,process1(i,i+n-1)); mmax=max(mmax,process2(i,i+n-1)); } cout<<mmin<<endl<<mmax; return 0; }