赞
踩
区间DP属于线性DP的一种,以区间长度作为DP的阶段,以区间的左右端点作为状态的维度。一个状态通常由被它包含且比它更小的区间状态转移而来。阶段(长度),状态(左右端点),决策三者按照由外到内的顺序构成3层循环。
接下来介绍一个典型问题。
有N堆石子排成一排,每堆石子有一定的数量。先要将2堆石子合并成一堆,每次合并花费的代价为这两堆石子的和,经过N-1次合并后称为一堆。求总的代价最小值。
问题分析:
1.求某个区间的最优解[1,n];
2.大的区间由包含于它的小区间组成(转移而来);
3.满足DP的三个基本条件。
状态
通用状态的定义:DP[i][j]从i到j区间的最优解。
目标状态的表示:DP[1,n]
阶段的划分:区间长度
决策(状态转移)
dp[i][j]=min(dp[i][j],dp[i][k]+dp[k+1][j]+w[i][j])
w[i][j]是从i到j区间总的石子重量,可以用前缀和求得。
**区间DP:**小结:在区间上动态规划,具体的说就是,现在小区间进行DP得到小区间的最优解,然后通过合并小区间的最优解,进而求出大区间的最优解。
阶段的划分:区间长度
状态的表示:枚举起点(不同起点,不同状态)
决策的实现:枚举分割点
区间DP的状态设计相对简单,基本大部分问题都适用。
DP[i][j]代表区间[i,j]上的最优解。
状态转移一定是从区间长度短的状态到区间长度长的状态。
模板
迭代,时间复杂度
(
O
(
n
3
)
)
(O(n^3))
(O(n3))
for(int len=2;len<=n;len++) //区间长度
{
for(int i = 1;i<=n;i++) //枚举起点
{
int j=i+len-1; //计算区间终点
if(j>n) break; //越界结束
for(int k = i;k<j;k++) //枚举分割点,构造状态转移方程
{
dp[i][j]=min(dp[i][j],dp[i][k]+dp[k+1][j]+w[i][j];
}
}
}
记忆化DFS:时间复杂度 ( O ( n 2 ) ) (O(n^2)) (O(n2))
int MINdfs(int l,int r) //推荐记忆化DFS的实现方式,更简单,不容易出错。
{
int &D = dp[l][r]; //引用D就代表dp[l][r],这个方法可以简化代码
//inf是自定义的整型最大值
if(D!=inf)return D; //如果已经搜索过就返回这个值,记忆化搜索的重点
if(l == r) return D=0; //如果l==r,无需合并,所以返回0
for(int i = l;i<r;i++) //枚举所有可行的区间分割方案
D=min(D,MINdfs(l,i)+MINdfs(i+1,r)+sum[r]-sum[l-1];
return D;
}
题目Cheapest Palindrome
题意:给出一个字符串,以及添加或删除部分字符的成本,求将字符串变为回文串所需的最小成本。只能添加或删除给出成本的字符。
思路:区间DP,用dp[i][j]表示将字符串下标i到j的子串变为回文串所需的最小成本,无非是在左端删除一个字符或者在右端添加一个字符。两种情况,如果s[i]等于s[j]就不需要进行操作,dp[i][j] = dp[i+1][j-1],如果s[i]不等于s[j],要么在右端添加字符s[i]要么在左端删除字符s[i],两种情况取最小值dp[i][j] = min(dp[i+1][j]+w[str[i]-‘a’],dp[i][j-1]+w[str[j]-‘a’])
AC代码:
/* **Author:skj **Time:8-6 **Function:区间DP poj3280 */ //#include <bits/stdc++.h> #include <string> #include <iostream> #define INF 0x3f3f3f3f using namespace std; const int maxm = 2002; int dp[maxm][maxm]; int w[27]; int main() { freopen("in.txt","r",stdin); freopen("out.txt","w",stdout); cin.sync_with_stdio(0); cin.tie(0); string str; int n,m; cin >> n >> m; cin >> str; while(n--) { char c; int a,b; cin >> c; cin >> a >> b; w[c-'a'] = min(a,b);//因为对于一个字符的操作不是删除就是添加,而且不需要知道最终的字符串,所以到底是删除了字符还是添加了字符就不需要知道的很确切,这里只需要保存一个最小值就可以了 } for(int i = m-1;i >= 0;i--)//从字符串最右侧开始是为了确保能正确遍历dp数组 { for(int j = i+1;j < m;j++) { if(str[i]==str[j]) { dp[i][j] = dp[i+1][j-1]; } else { dp[i][j] = min(dp[i+1][j]+w[str[i]-'a'],dp[i][j-1]+w[str[j]-'a']); } } } cout << dp[0][m-1] << endl; return 0; }
题目地址POJ 2955 Brackets
题意:正则括号序列,就是从语法上来说合法的括号序列,类似(),()[(())]都可以,但是[(])不可以,给出一个序列,问他的子序列中最长的正则括号序列长度
思路:区间DP,与回文字符串思路基本一样,但是不同的是[(([]))]这种情况,需要处理,在代码中说明。
AC代码:
/* **Author:skj **Time:8-7 **Function:poj 2955 区间DP 括号匹配 */ //#include <bits/stdc++.h> #include <iostream> #include <string> #include <cstring> #define INF 0x3f3f3f3f using namespace std; const int maxn = 101; int dp[maxn][maxn]; bool judge(char c1,char c2) { if(c1 == '('&&c2==')') { return true; } else if(c1=='['&&c2==']') { return true; } else if(c1=='{'&&c2=='}') { return true; } return false; } int main() { // freopen("in.txt","r",stdin); // freopen("out.txt","w",stdout); cin.sync_with_stdio(0); cin.tie(0); string s; cin >> s; while(s!="end") { int l = s.size(); for(int i = l-1;i>=0;i--) { for(int j = i+1;j<l;j++) { if(judge(s[i],s[j])) { dp[i][j] = dp[i+1][j-1]+2; } for(int k = i;k<j;k++)//处理[()]([()])这种情况,需要遍历所有子问题之和取最大值。 { dp[i][j] = max(dp[i][j],dp[i][k]+dp[k+1][j]); } } } cout << dp[0][l-1] << endl; memset(dp,0,sizeof(dp)); cin >> s; } return 0; }
题目地址【HDU 3506 Monkey Party】
题意:N只猴子坐成环,都不认识对方,森林之王每次介绍两只猴子A和B,A和B相邻,A认识的每只猴子都将认识B已经认识的每只猴子,介绍的总时间是A和B已经认识的所有猴子交友时间的总和。每只猴子都认识自己。求最短的介绍时间。
思路:跟合并石子的题目有点像,属于环型的石子合并。
属于区间DP,n只猴子坐在一个圈里,是一个环,对于包含n个元素的环型,可以将前n-1个元素一次复制到第n个元素后面,将环型转化为直线型。
a1,a2,···
a
n
a_n
an,a1···
a
n
−
1
a_{n-1}
an−1,然后以长度作为阶段,以序列的开始和结束下标作为状态的维度,通过不同的情况执行不同的决策。
状态表示:dp[i][j]表示[i,j]区间猴子相互认识的最少时间。枚举每一个位置k,求两个子问题之和的最小值。
状态转移方程:dp[i][j]=min(dp[i][k]+dp[k+1][j]+sum(i,j))。
最后从规模是n的最优值中找出最小值即可。
该算法的时间复杂度为
O
(
n
3
)
O(n^3)
O(n3),数据范围
1
≤
n
≤
1000
1\le n \le1000
1≤n≤1000,超时,考虑采用四边不等式优化。
求解dp[i][j]时,需要枚举位置k=i,···,j-1,用s[i][j]记录dp[i][j]取得最小值的位置k。利用四边形不等式优化后,k的枚举范围变为s[i][j-1]~s[i+1][j],枚举范围小了很多。
#include<bits/stdc++.h> using namespace std; int a[2010];//存前缀和 int dp[2010][2010]; int s[2010][2010];//记录最优分割点 int inf=0x3f3f3f3f; int main(){ int n; while(cin>>n){ for(int i=1;i<=n;i++){ cin>>a[i]; a[i+n]=a[i]; } memset(dp,inf,sizeof(dp)); for(int i=1;i<=n*2;i++) a[i]+=a[i-1]; for(int i=1;i<=n*2;i++){ dp[i][i]=0; s[i][i]=i; } for(int len=1;len<n;len++){ for(int i=1;i<=n;i++){ int j=i+len; for(int k=s[i][j-1];k<=s[i+1][j];k++){ if(dp[i][k]+dp[k+1][j]+a[j]-a[i-1]<dp[i][j]){ dp[i][j]=dp[i][k]+dp[k+1][j]+a[j]-a[i-1]; s[i][j]=k; } } } } int m=1e9; for(int i=1;i<=n;i++){ m=min(m,dp[i][i+n-1]); } cout<<m<<endl; } }
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。