学习内容:
滚动数组:
滚动数组是DP中的一种编程思想。简单的理解就是让数组滚动起来,每次都使用固定的几个存储空间,来达到压缩,节省存储空间的作用。起到优化空间,主要应用在递推或动态规划中。因为DP题目是一个自底向上的扩展过程,我们常常需要用到的是连续的解,前面的解往往可以舍去。所以用滚动数组优化是很有效的。利用滚动数组的话在N很大的情况下可以达到压缩存储的作用。
节省了空间。
区间动态规划:
区间动态规划,顾名思义,就是动态规划过程中求一个区间的最优解。通过将一个大的区间分为很多个小的区间,求其小区间的解,然后一个一个的组合成一个大的区间而得出最终解。
第一种模型:
石子合并:
石子合并问题是最经典的DP问题。首先它有如下3种题型:
(1)有N堆石子,现要将石子有序的合并成一堆,规定如下:每次只能移动任意的2堆石子合并,合并花费为新合成的一堆石子的数量。求将这N堆石子合并成
分析:当然这种情况是最简单的情况,合并的是任意两堆,直接贪心即可,每次选择最小的两堆合并。
(2)有N堆石子,现要将石子有序的合并成一堆,规定如下:每次只能移动相邻的2堆石子合并,合并花费为新合成的一堆石子的数量。求将这N堆石子合并成一堆的总花费最小(或最大)。
设dp[i][j]表示第i到第j堆石子合并的最优值,sum[i][j]表示第i到第j堆石子的总数量。那么就有状态转移公式:
#include<cstring>
#include<cmath>
#include<algorithm>
#include<bits/stdc++.h>
using namespace std;
//#define Max 0x3f
int sum[1001];
int n,i,j,k,x;
int dp[1001][1001];
int main()
{
cin>>n;
for(i=1; i<=n; i++)
{
cin>>x;
sum[i]=sum[i-1]+x;
}
memset(dp, 0x3f, sizeof(dp));
for (int i = 1; i <= n; i++)
dp[i][i] = 0;
for (int len = 2; len <= n; len++)
{
for (int i = 1, j = len; j <= n; i++, j++)
{
for (int k = i; k < j; k++)
{
if(dp[i][j] > dp[i][k] + dp[k+1][j] + sum[j] - sum[i-1])
dp[i][j] = dp[i][k] + dp[k+1][j] + sum[j] - sum[i-1];
}
}
}
printf("%d\n", dp[1][n]);
}
(3)问题(2)的是在石子排列是直线情况下的解法,如果把石子改为环形排列。
以后补充……
第二种模型:
括号匹配:
#include<algorithm>
#include<iostream>
#include<cstdio>
#include<cstring>
#define ll long long
#define maxn 105
using namespace std;
int dp[maxn][maxn];
char s[maxn];
int dfs(int l,int r){
if(l>=r) return 0;
if(dp[l][r] != -1) return dp[l][r];
if(s[l]=='('&&s[r]==')'||s[l]=='['&&s[r]==']')
dp[l][r]=dfs(l+1,r-1)+2;
for(int i=l;i<r;i++)
dp[l][r]=max(dp[l][r],dfs(l,i)+dfs(i+1,r));
return dp[l][r];
}
int main(){
int n;
while(~scanf("%s",s)&&s[0]!='e'){
int n=strlen(s);
memset(dp,-1,sizeof(dp));
int ans=dfs(0,n-1);
printf("%d\n",ans);
}
return 0;
}
第三种模型:
不需要枚举区间k,只和左右边界相关。
例题:
令dp[i][j]表示从位置i到j的子串转化为回文串需要的次数若 s[i]== s[j] 则dp[i][j] = dp[i + 1][j - 1]否则 dp[i][j] = min(dp[i+1][j] + cost[i], dp[i][j - 1] + cost[j])
#include<iostream>
#include<cstring>
#include<cstdio>
#include<algorithm>
#define MAX 2111
using namespace std;
int dp[MAX][MAX];
int cost[300];
char ch[MAX];
int N,M;
int DP()
{
int i,j,k,r;
for(i=0;i<M;i++) dp[i][i]=0;
for(r=2;r<=M;r++) //子段的长度
for(i=0;i<=M-r;i++) //i为首指针,j为尾指针。
{
j=i+r-1;
if(ch[i]==ch[j])
dp[i][j]=dp[i+1][j-1];
else
{
dp[i][j]=min(dp[i+1][j]+cost[ch[i]],dp[i][j-1]+cost[ch[j]]);
}
}
return dp[0][M-1]; //最优解
}
int main()
{
int i,j;
char c;
int ins,del;
scanf("%d%d",&N,&M);
getchar();
gets(ch);
for(i=0;i<N;i++)
{
scanf("%c%d%d",&c,&ins,&del);
getchar();
cost[c]=min(ins,del);
}
int ans=DP();
printf("%d\n",ans);
return 0;
}
unique去重
unique函数属于STL中比较常用函数,它的功能是元素去重。即”删除”序列中所有相邻的重复元素(只保留一个)。此处的删除,并不是真的删除,而是指重复元素的位置被不重复的元素给占领了(详细情况,下面会讲)。由于它”删除”的是相邻的重复元素,所以在使用unique函数之前,一般都会将目标序列进行排序。
unique函数的函数原型如下:
1.只有两个参数,且参数类型都是迭代器:
iterator unique(iterator it_1,iterator it_2);
这种类型的unique函数是我们最常用的形式。其中这两个参数表示对容器中[it_1,it_2)范围的元素进行去重(注:区间是前闭后开,即不包含it_2所指的元素),返回值是一个迭代器,它指向的是去重后容器中不重复序列的最后一个元素的下一个元素。
unique函数的去重过程实际上就是不停的把后面不重复的元素移到前面来,也可以说是用不重复的元素占领重复元素的位置。
2.unique函数通常和erase函数一起使用,来达到删除重复元素的目的。这里的删除是真正的删除。