【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
Output
Input示例
Output示例
好经典的一个区间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;
}
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;
}
相关文章推荐
- 数组-在Shell脚本中的基本使用介绍
- linux配置使用外部smtp发送邮件
- HDU 5831 Rikka with Parenthesis II (贪心) -2016杭电多校联合第8场
- Spring面试题集
- 顺序栈
- VC编程操作word2010生成表格
- PHP学习笔记总结 转
- AOS 自动生成代码(二) Dao生成
- Java 中的Unicode与PrintWriter
- 日常一水
- 链表
- C++对象的动态建立和释放
- Java中Enumeration接口讲解
- 前端人应该知道的排序知识
- 深度学习主机环境配置: Ubuntu16.04+GeForce GTX 1080+TensorFlow
- Android studio清除项目中无用的资源
- VC编程操作word2010生成表格
- .NET框架
- spring源码学习之【准备】jdk动态代理例子
- 【JEECG Docker安装】Docker启动报错