洛谷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:

 1 //It is made by HolseLee on 23rd May 2018
 2 //Luogu.org P1880
 3 #include<bits/stdc++.h>
 4 #define Fi(i,a,b) for(int i=a;i<=b;i++)
 5 #define Fx(i,a,b) for(int i=a;i>=b;i--)
 6 using namespace std;
 7 const int N=1001;
 8 int n,a[N],s[N],d[N][N],x[N][N];
 9 int maxx,minn=0x3f3f3f3f;
10 int main()
11 {
12   ios::sync_with_stdio(false);
13   cin>>n;Fi(i,1,n){cin>>a[i];a[n+i]=a[i];}
14   Fi(i,1,2*n)s[i]=s[i-1]+a[i];
15   Fi(c,1,n){
16     memset(x,0x7f,sizeof(x));
17     memset(d,0,sizeof(d));
18     Fi(i,c,c+n-1)d[i][i]=x[i][i]=0;
19     Fx(i,c+n-2,c)Fi(j,i+1,c+n-1)Fi(k,i,j-1){
20       d[i][j]=max(d[i][j],d[i][k]+d[k+1][j]+s[j]-s[i-1]);
21       x[i][j]=min(x[i][j],x[i][k]+x[k+1][j]+s[j]-s[i-1]);}
22     maxx=max(maxx,d[c][c+n-1]);
23     minn=min(minn,x[c][c+n-1]);}
24   cout<<minn<<"\n"<<maxx;return 0;
25 }

猜你喜欢

转载自www.cnblogs.com/cytus/p/9076523.html