其他分享
首页 > 其他分享> > 合并石子题解

合并石子题解

作者:互联网


Hello everybody!

- 请奆佬们洁身自好,好好打代码从我做起 -

题目大意:

设有 N堆石子排成一排,其编号为 1,2,3,…,N

每堆石子有一定的质量,可以用一个整数来描述

现在要将这 N堆石子合并成为一堆,每次只能合并相邻的两堆

合并的代价为这两堆石子的质量之和

合并后与这两堆石子相邻的石子将和新堆相邻

合并时由于选择的顺序不同,合并的总代价也不相同

找出一种合理的方法,使总的代价最小,输出最小代价

同时输出合并过程


合并石子其实和 合并果子
很像

只是合并 只能相邻,但其实也方便了 区间dp

从最后一次合并开始思考

最后一次合并前,石子肯定只有两堆

设k为两者 中点也就是两堆中间的位置

第一堆是原来的1到k堆,第二堆是原来的第k+1到第n堆。

显然,

要使得总代价最小,必然是该两堆式子之前的合并代价最小。

然后不断拆分成子问题解决


定义状态f(i,j)

表示 第i堆石子到第j堆石子最小合并代价

目标状态即为f(1,n)。

方程——f(i,j)=min(f(i,k)+f(k+1,j)+sum(i,j);

sum是什么呢?是一个 区间和 ,我们利用 前缀和 可以将询问sum的复杂度降至 O(1)

int query(int i, int j) //前缀和,sum[i],表示1~i的和,数学内容,简单,自行思考原理 
{ 
	return sum[j] - sum[i - 1]; 
}

为什么是 区间和 呢?

其实 区间和就是两堆的重量(简单,自行思考)

也就是 合并代价


然后是 初始化

因为是求最小值,所以要开一个大数

memset(f, 27, sizeof(f));

27其实计算机会认为是一个很大的数,接近int边界,并不是我们认为的27

也可以换成其他数


    for (int i = 1; i <= n; i++) 
	{
        cin >> a[i];
        sum[i] = sum[i - 1] + a[i];// 预处理前缀和
        f[i][i] = 0;//自己合并自己不需要代价 
    }

输入可以同时处理

见代码,sum可以求,因为自己合并自己不需要代价 ,所以f(i,i)始终为0


接下来讲解dp部分

由于我们已经知道起点和区间大小,所以可以求终点

然后枚举中点就完了

    for (int k = 2; k <= n; k++)//k,枚举区间大小 
	{ 这个p是什么呢?

p数组用来记录最优解的中点 

Why?


        for (int i = 1; i <= n; i++) //i,枚举起点 
		{
            int j = i + k - 1;//求终点 
            for (int l = i; l < j; l++)//枚举中点,这里可以优化,详见PPT《DP之四边形不等式优化》 
			 {
                if (f[i][j] > f[i][l] + f[l + 1][j] + query(i, j)) //求最小值 
				{
                    f[i][j] = f[i][l] + f[l + 1][j] + query(i, j);
                    p[i][j] = l;//有新的最小值,更新中点 
                }
            }
        }
    }

这个p是什么呢?

p数组用来记录最优解的中点

Why?


由于题目要求过程,我们可以用递归求解

这时p就派上用场了

p(i,j)表示合并i,j时的中点k

由此将l,r分成两份,不断递归


void dg(int l, int r) 
{
    if (l == r) {//说明只有一个数,直接输出! 
        cout << a[r];//不要换行!不要空格! 
        return;
    }
    cout << '(';//输出左右括号 
    int mid = p[l][r];//读取中点 
    dg(l, mid);//递归左半部分 
    cout << ")(";//输出中间括号 
    dg(mid + 1, r);//递归右半部分
    cout << ')';//输出左右括号 
}

好了,这就是关于合并石子的全部内容了

上代码!

#include <bits/stdc++.h>
using namespace std;
int n, a[1005], f[1005][1005], p[1005][1005], sum[1005];
int query(int i, int j) 
{ 
	return sum[j] - sum[i - 1]; 
}
void dg(int l, int r) 
{
    if (l == r) {
        cout << a[r];
        return;
    }
    cout << '(';
    int mid = p[l][r];
    dg(l, mid);
    cout << ")(";
    dg(mid + 1, r);
    cout << ')';
}
int main() {
    memset(f, 27, sizeof(f));
    cin >> n;
    for (int i = 1; i <= n; i++) 
	{
        cin >> a[i];
        sum[i] = sum[i - 1] + a[i];
        f[i][i] = 0;
    }
    for (int k = 2; k <= n; k++)
	{ 
        for (int i = 1; i <= n; i++) 
		{
            int j = i + k - 1;
            for (int l = i; l < j; l++)
			 {
                if (f[i][j] > f[i][l] + f[l + 1][j] + query(i, j)) 
				{
                    f[i][j] = f[i][l] + f[l + 1][j] + query(i, j);
                    p[i][j] = l;
                }
            }
        }
    }
    cout << f[1][n] << endl; 
    dg(1, n);
    return 0;
}

其实可以 优化 ,详见《 四边形不等式 优化》

但那是搞n=5000的大数据的......

版权归Joker所有,禁止未经作者同意搬运!

标签:int,题解,sum,石子,合并,两堆,代价
来源: https://www.cnblogs.com/I-am-joker/p/15230358.html