dp分类及习题
印象头以前还没有接触过区间dp
,就在网上找了一哈讲解,但是基本上的都是大概介绍几句以后就开始上代码了,有点坐飞机的感觉,然后做了两道稍微有点感觉。。
区间dp总结篇
接下来三道题相当于是一个系列,一道比一道难一点点从裸题,到有环,最后到优化
codevs1048
这个石子归并的题总感觉好像在哪见过,后头做的时候,做到做到突然想起来,这是学校头老实讲算法分析与设计讲过的的一个例题,还记得当时看书看了半天才理解到,现在好些了
#include
#include
#include
using namespace std;
#define maxn 105
int shuzu[maxn][maxn];
int arr[maxn];
int main(){
int n, input;
cin >> n;
memset(shuzu, 0x3f, sizeof(shuzu));
memset(arr, 0, sizeof(arr));
for(int i = 1; i <= n; i ++){
scanf("%d", &input);
arr[i] = input;
arr[i] += arr[i-1];
shuzu[i][i] = 0;
}
for(int len = 1; len < n; len ++){
for(int i = 1; i <= n-len; i ++){
int j = i + len;
for(int k = i; k < j; k ++){
int temp = shuzu[i][k] + shuzu[k+1][j] + arr[j] - arr[i-1];
shuzu[i][j] = shuzu[i][j] < temp ? shuzu[i][j] : temp;
}
}
}
cout << shuzu[1][n] << endl;
return 0;
}
codevs1154
这道题我虽然做出来了,但是办法有点瓜,这道题主要就是有个环,我自己想的是再加一层循环就行了,虽然最后还是ac了,但是确实太菜了,后头我看网上的解说,高手用的是直接展开呈线性的就行了。。
但是有个要注意的地方,就是更新要到位,比如说现在已经在更新长度为2的区间的时候,那长度为1的区间一定要确保先就更新完了
四边形不等式优化
DP优化——四边形不等式(简介)
如果只是用朴素的方法,复杂度是O(n3)的,上面两道题因为数据太菜所以还是可以勉强过,但是数据稍微大一点,就不得不涉及到优化了,优化我在网上就只看到了通过四边形不等式得出决策递增(也就是k(分割点)递增),然后复杂就降到O(n2)了!很神奇,但是证明很麻烦
这是百度百科上给出的证明步骤,缺一不可,一看脑壳都大了,而下面这位高手详细的证明了这三步,在配上百度百科的证明,百度百科的比较简洁
四边形不等式优化的详细证明
四边形不等式—百度百科
说实话我还是认真看了哈,但是确实感觉不透彻,要想自己证明是不可能的,关键是如果不证明自己用起心头都是虚的,但是网上确实也有人说可以绕过,比如这篇文章
【教程】四边形不等式学习笔记
也就是通过打表,自己切观察是不是单调的,但这个样子心头还是虚的,确实有点恼火。。
HDU3506
这道题就是数量级达到了O(10003),所以如果还是只用普通的区间dp肯定超时,所以就要用到上面的四边形不等式优化,虽然证明可以用这个优化比较麻烦,但是还是很简洁的
#include
#include
#include
using namespace std;
#define maxn 2005
#define inf 0x3f3f3f3f
int n;
int shuzu[maxn][maxn], s[maxn][maxn];
int input[maxn], sum[maxn];
int main(){
while(~scanf("%d", &n)){
memset(sum, 0, sizeof(sum));
memset(s, 0, sizeof(s));
memset(shuzu, 0x3f, sizeof(shuzu));
for(int i = 1; i <= n; i ++){
scanf("%d", &input[i]);
input[i+n] = input[i];
shuzu[i][i] = shuzu[i+n][i+n] = 0;
}
for(int i = 1; i <= 2*n; i ++) {
sum[i] += sum[i-1] + input[i];
s[i][i] = i;
}
for(int len = 1; len < n; len ++){
for(int i = 1; i < 2*n-len; i ++){
int j = i + len;
int kk;
for(int k = s[i][j-1]; k <= s[i+1][j]; k ++){
int temp = shuzu[i][k] + shuzu[k+1][j];
if(shuzu[i][j] > temp){
shuzu[i][j] = temp;
kk = k;
}
}
shuzu[i][j] += sum[j] - sum[i-1];
s[i][j] = kk;
}
}
// print();
int answer = inf;
for(int i = 1; i <= n; i ++)
if(answer > shuzu[i][i+n-1]) answer = shuzu[i][i+n-1];
cout << answer << endl;
}
return 0;
}
/*
8
5 2 4 7 6 1 3 9
*/
POJ2955
这道题括号匹配问题和石子归并还是有点像,不同就是除了石子归并那一个比较之外还有将每次dp区域的左右两端的边界拿出来单独比较是否成对
POJ1159
这道题我看到n<=5000的第一反应就是多半要用四边形优化,因为这的数量级的O(n3)肯定超时,但是最扯的是我第一次提交结果居然是超内存MLE了,因为我还是按上面题的套路,空间复杂度是O(n2),按理来说他题目个体的是65535K应该是够的,反正空间要优化,我就切网上看了一哈大神的讲解。
学到个滚动数组优化空间复杂度,其实这个滚动数组之前的多重背包用过这个技巧。而且要用这个滚动数组有个前提条件,就是状态转移方程必须只能是相近行之间有关系,因为只用得到最近的几行,所以就可以边用边滚节约空间
而且用了滚动数组过后,时间复杂度也降下来了,不用再切考虑啥子四边形不等式优化了
POJ1141
这道题用g++就超时,c++就过了,虽然编译器有速度上的差异,但是我在本地测最多只用30+ms,我就以为是不是啥子变量没有初始化,或者数组越界导致的,最后我觉得多半是用了string导致的,我也不是很清楚,方正我看网上高手的方法都不简单,我只是简单地把所有对应的下标都存下来,但是高手的方法不一样,是通过每次保存断点
POJ 1141 Brackets(路径记录)
这篇文章的思路比较清晰,看到保存断点的时候就晓得了。
POJ1651
这道题就是把状态转移方程找到就行了
POJ3280
这道题和POJ1159很像,也是用滚动数组优化,状态转移方程都是一样的,就只有有一个cost,稍微处理一哈就行了,一回事
POJ3661
第一眼看到10000的数据我就想多半要用滚动数组,然后想出来一个很恶心的dp,写出来的代码也很恶心,最后超时了,后头转变了一哈思路,瞬间代码、思路都简洁明了
#include
#include
#include
using namespace std;
#define maxn 10005
int n, m;
int input[maxn], sum[maxn];
int shuzu[maxn];
int main(){
scanf("%d%d", &n, &m);
if(n/2 < m) m = n / 2;
for(int i = n; i >= 1; i --) scanf("%d", &input[i]);
sum[0] = 0;
for(int i = 1; i <= n; i ++) sum[i] = sum[i-1] + input[i];
memset(shuzu, 0, sizeof(shuzu));
for(int i = 1; i <= n; i ++){
shuzu[i] = shuzu[i-1];
for(int j = 1; j<=m && 2*j<=i; j ++){
int temp = sum[i] - sum[i-j] + shuzu[i-2*j];
shuzu[i] = shuzu[i] > temp ? shuzu[i] : temp;
}
}
// for(int i = 1; i <= n; i ++) cout << shuzu[i] << " " ; cout << endl;
cout << shuzu[n] << endl;
return 0;
}
/*
5 2
5 3 4 2 10
6 3
3 3 3 1 1 1
4 2
3 3 1 1
*/