您的位置:首页 > 其它

【51Nod】1021 - 石子归并(区间dp & 四边形不等式优化)

2016-08-11 17:55 204 查看
点击打开题目

1021 石子归并


基准时间限制:1 秒 空间限制:131072 KB 分值: 20 难度:3级算法题


 收藏


 取消关注

N堆石子摆成一条线。现要将石子有次序地合并成一堆。规定每次只能选相邻的2堆石子合并成新的一堆,并将新的一堆石子数记为该次合并的代价。计算将N堆石子合并成一堆的最小代价。

例如: 1 2 3 4,有不少合并方法
1 2 3 4 => 3 3 4(3) => 6 4(9) => 10(19)
1 2 3 4 => 1 5 4(5) => 1 9(14) => 10(24)
1 2 3 4 => 1 2 7(7) => 3 7(10) => 10(20)

括号里面为总代价可以看出,第一种方法的代价最低,现在给出n堆石子的数量,计算最小合并代价。

Input
第1行:N(2 <= N <= 100)
第2 - N + 1:N堆石子的数量(1 <= A[i] <= 10000)


Output
输出最小合并代价


Input示例
4
1
2
3
4


Output示例
19


好经典的一个区间dp的题啊!不过这次在纸上画了画,自己都想的差不多了,但是自己只写了个O(n^3)的算法,后来看了看,发现还有一个用四边形不等式优化的方法,也学习了学习,时间优化到了O(n^2)。

好了不废话了,第一次写一个类型题的时候,都会仔细的写一篇讲解的:

要求是把n堆石子合并成一堆,但是只能合并相邻的,所以不能用贪心的方法了。

然后我们考虑简化问题的方法,即:把n-1堆石子合并成一堆求最少耗费,然后再和剩下的一堆合并。

然后再次简化:把n-2堆石子合并成一堆,再和剩下的2堆合并。

……

……

……

最后问题就成了,把2堆石子合并。

然后我们再开始正序往后想,两堆石子的合并想必都会,就是从1 ~ n-1 依次枚举。

那么三堆呢?

比如 1 2 3 三堆,我们可以分开,考虑:dp[1][3] = min(dp[1][2] + dp[3][3] , dp[1][1] + dp[2][3]) + sum[1,3]

那么四堆呢?

我们再分区间 [1,1]+[2,3] 和 [1,2] + [3,4] 和 [1,3] + [4,4] 最小值加上一个sum[1,4]就是移动这四堆所需要的最小耗费。

现在再看是不是清晰了点,那么我我们先看n^3的方法:

三个for:

①枚举长度l:2 ~ n

②枚举起始端点:1 ~ n - l + 1

③枚举断点位置(就是上面的把区间拆开的点的位置):i ~ endd(endd为末端点,等于i + l - 1)

代码如下:(O(n^3))(下面还有优化的算法)

#include <stdio.h>
#include <cstring>
#include <algorithm>
using namespace std;
#define CLR(a,b) memset(a,b,sizeof(a))
#define INF 0x3f3f3f3f
#define LL long long
int main()
{
int n;
int t;
int dp[111][111];
int sum[111];
sum[0] = 0;
scanf ("%d",&n);
for (int i = 1 ; i <= n ; i++)
scanf ("%d",&t) , sum[i] = sum[i-1] + t;
for (int i = 1 ; i <= n ; i++)
dp[i][i] = 0;
for (int l = 2 ; l <= n ; l++) //选取区间长度
{
for (int i = 1 ; i <= n-l+1 ; i++)
{
int endd = i + l - 1; //区间末端点
dp[i][endd] = INF;
for (int k = i+1 ; k <= endd ; k++)
dp[i][endd] = min(dp[i][endd] , dp[i][k-1] + dp[k][endd] + sum[endd] - sum[i-1]);
}
}
printf ("%d\n",dp[1]
);
return 0;
}

知道了基本的,然后我们来谈优化,优化其实很好想,每次我们找断点k的时候,都是从i ~ endd一个个枚举。我们是否可以用一个方法快速找到呢?答案是可以的,这里有一个四边形不等式:点击打开链接

也就是说,我们用一个数组s[][]记录从 i 到 j 断点的位置,那么下一轮,我们就找这两个断点中间的点:

s[i][j-1] <= k <= s[i+1][j]    (注意,这两个s的数值在算 l - 1 的时候已经算出来了)

这个时候这个k就基本确定在那一两个数中了。时间复杂度成功优化,当然多开了一个s数组占用了空间复杂度,但是是没办法的事,本来就是矛盾嘛。

代码如下:

#include <stdio.h>
#include <cstring>
#include <algorithm>
using namespace std;
#define CLR(a,b) memset(a,b,sizeof(a))
#define INF 0x3f3f3f3f
#define LL long long
int main()
{
int n;
int t;
int dp[111][111];
int s[111][111];
int sum[111];
sum[0] = 0;
scanf ("%d",&n);
for (int i = 1 ; i <= n ; i++)
{
scanf ("%d",&t);
sum[i] = sum[i-1] + t;
s[i][i] = i;
}
CLR(dp,0);
for (int l = 2 ; l <= n ; l++) //选取区间长度
{
for (int i = 1 ; i <= n-l+1 ; i++)
{
int endd = i + l - 1; //区间末端点
dp[i][endd] = INF;
for (int k = s[i][endd-1] ; k <= s[i+1][endd] ; k++) //利用四边形不等式优化范围
{
if (dp[i][endd] > dp[i][k] + dp[k+1][endd] + sum[endd] - sum[i-1]) //就算这里k+1超了也不怕
{
dp[i][endd] = dp[i][k] + dp[k+1][endd] + sum[endd] - sum[i-1];
s[i][endd] = k;
}
}
}
}
printf ("%d\n",dp[1]
);
return 0;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: