1274:【例9.18】合并石子

  • 找题目?故作深沉的传送门
  • 看完题数据规模一定是不能暴力的,所以还有两条路:贪心 or 动规
  • 至于贪心的可行性…
  • 让我们看一组数据(样例)实在想不出别的反例了
    7
    13 7 8 16 21 4 18
  • 按照贪心(每次都合并最小值)算一算,结果应该是248本来画了图,太丑不敢放
  • 但是,样例输出是239,所以贪心不可行
  • 接下来就剩动规一种选择了
  • 状态转移方程:
    f [ i ] [ j ] = m i n ( f [ i ] [ j ] , f [ i ] [ k ] + f [ k + 1 ] [ j ] + a [ j ] − a [ i − 1 ] ) ; f[i][j]=min(f[i][j],f[i][k]+f[k+1][j]+a[j]-a[i-1]); f[i][j]=min(f[i][j],f[i][k]+f[k+1][j]+a[j]a[i1]);
  • f 数组是存储区间内相加最小值
  • f[i][j] 是从 ij 的区间内的相加的最小值
  • kij 区间最后一次相加时相加点
  • a 数组是存储前缀和
  • 注意:k 要加1,i 要减1。
  • 敲黑板,重点注意:下面三层循环中的 i 为倒序,原因如下:
    • 这道题动规的思想是把一个大问题分解成若干子问题,而这种情况下应当先求子问题,
    • 究其原因是因为动规的每一步都是根据前一步推导而出,所以大问题的根据是子问题,
    • 如果 i 是正序的话,那么 ij 之间的区间会越来越小,不符合动规的本质,所以是倒序
#include
using namespace std;
int n,m,a[1001],f[1001][1001];
int main(){
	scanf("%d",&n);
	memset(f,127,sizeof(f)); //初始化
	for(int i=1;i<=n;i++){
		scanf("%d",&a[i]);
		a[i]+=a[i-1]; //求前缀和
		f[i][i]=0; //初始化
	}
	for(int i=n;i>=1;i--){ //一定要倒序枚举
		for(int j=i+1;j<=n;j++){ 
			for(int k=i;k<j;k++) //寻找i,j之间的最小合并点
				f[i][j]=min(f[i][j],f[i][k]+f[k+1][j]+a[j]-a[i-1]);
		}
	}
	cout<<f[1][n]<<endl;
	return 0;
}

你可能感兴趣的:(动态规划)