Evanyou Blog 彩带

洛谷P1880 [NOI1995] 石子合并 [DP,前缀和]

  题目传送门

题目描述

在一个圆形操场的四周摆放N堆石子,现要将石子有次序地合并成一堆.规定每次只能选相邻的2堆合并成新的一堆,并将新的一堆的石子数,记为该次合并的得分。

试设计出1个算法,计算出将N堆石子合并成1堆的最小得分和最大得分.

输入输出格式

输入格式:

 

数据的第1行试正整数N,1≤N≤100,表示有N堆石子.第2行有N个数,分别表示每堆石子的个数.

 

输出格式:

 

输出共2行,第1行为最小得分,第2行为最大得分.

 

输入输出样例

输入样例#1: 复制
4
4 5 9 4

  分析:显然是用DP。

  由于是环状,我们需要把它转换为链状,那么就把存储石子的数组a[]空间开大一倍,从n+1~2*n存储的值等于1~n存储的值,那么只需要从1到n枚举链的开头即可,链尾则分别为n到2*n-1。这样计算和环状是等效的。

  那么考虑状态转移方程,设d[i][j]是将i~j堆石子合并后得到的最大值,x[i][j]是将i~j堆石子合并后得到的最小值,首先两重循环i,j枚举区间的两端,然后再加一重循环枚举断点,也就是说,i~k和k+1~j分别是要合并的两堆石子,那么状态转移方程不难想到:

  d[i][j]=max(d[i][j],d[i][k]+d[k+1][j]+a[i]+...+a[j])

  x[i][j]=min(x[i][j],x[i][k]+x[k+1][j]+a[i]+...+a[j])

  显然两种计算不冲突,可以同时进行,然后a[i]+...+a[j]可以用前缀和优化,那么这题也就解决了。

  Code:

 

#include<bits/stdc++.h>
#define Fi(i,a,b) for(int i=a;i<=b;i++)
#define Fx(i,a,b) for(int i=a;i>=b;i--)
using namespace std;
const int N=1001;
int n,a[N],s[N],d[N][N],x[N][N];
int maxx,minn=0x3f3f3f3f;
int main()
{
  ios::sync_with_stdio(false);
  cin>>n;Fi(i,1,n){cin>>a[i];a[n+i]=a[i];}
  Fi(i,1,2*n)s[i]=s[i-1]+a[i];
  Fi(c,1,n){
    memset(x,0x7f,sizeof(x));
    memset(d,0,sizeof(d));
    Fi(i,c,c+n-1)d[i][i]=x[i][i]=0;
    Fx(i,c+n-2,c)Fi(j,i+1,c+n-1)Fi(k,i,j-1){
      d[i][j]=max(d[i][j],d[i][k]+d[k+1][j]+s[j]-s[i-1]);
      x[i][j]=min(x[i][j],x[i][k]+x[k+1][j]+s[j]-s[i-1]);}
    maxx=max(maxx,d[c][c+n-1]);
    minn=min(minn,x[c][c+n-1]);}
  cout<<minn<<"\n"<<maxx;return 0;
}

 

posted @ 2018-05-23 13:45  HolseLee  阅读(524)  评论(0编辑  收藏  举报