区间dp
概念:当在求解一个区间的问题答案是,我们可以先分成小的区间求解,在推导到大的区间,最后得出答案。
模板:
for(int len = 1;len<=n;len++){
for(int j = 1;j+len-1<=n;j++){
int ends = j+len-1;
for(int i = j;i<ends;i++){
dp[j][ends] = min(dp[j][ends],dp[j][i] + dp[i+1][ends] + sth);
}
}
}
例题:
石子归并1
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 <= Aii <= 10000)
Output
输出最小合并代价
Sample Input
4
1
2
3
4
Sample Output
19
思路:这题用区间dp,很容易可以得到转移式
dp[j] [ends] = min(dp[j] [ends],dp[j] [i] + dp[i+1] [ends] + 区间(j~ends)的和 );
#include<cstdio>
#include<cstring>
#include<cmath>
#include<cstdlib>
#include<iostream>
#include<algorithm>
#include<vector>
#include<set>
#include<map>
#include<stack>
#include<queue>
#define ll long long
//#define int 1e9
using namespace std;
int dp[109][109] = {0};
int a[109] = {0};
int sum[109] = {0};
int main(){
int n;
scanf("%d",&n);
for(int i = 1;i<=n;i++){
for(int j = 1;j<=n;j++){
dp[i][j] = 1e9;
}
}
for(int i = 1;i<=n;i++){
scanf("%d",&a[i]);
sum[i] = sum[i-1] + a[i];
dp[i][i] = 0;
}
for(int len = 1;len<=n;len++){
for(int j = 1;j+len-1<=n;j++){
int ends = j+len-1;
for(int i = j;i<ends;i++){
dp[j][ends] = min(dp[j][ends],dp[j][i] + dp[i+1][ends] + sum[ends]-sum[j-1]);
}
}
}
printf("%d",dp[1][n]);
return 0;
}
石子归并2
来源 codevs 2102
题意在1的基础上加上是成环的。
思路:假设给出样例:
6
1 2 3 4 5 6
那么我们可以知道他的长度肯定是6,并不会变得更长,成环可以看成他有6种不同的非成环区间例如:
1 2 3 4 5 6
2 3 4 5 6 1
3 4 5 6 1 2
4 5 6 1 2 3
5 6 1 2 3 4
6 1 2 3 4 5
所以我们只要把初始的序列增长为1 2 3 4 5 6 1 2 3 4 5在按照1的方法来求,但是我们要知道,区间的最长长度是6,要把每一个长度为6的区间都遍历一遍,看看最终的最大值。
(不知道是否ac的)代码:
#include<cstdio>
#include<cstring>
#include<cmath>
#include<cstdlib>
#include<iostream>
#include<algorithm>
#include<vector>
#include<set>
#include<map>
#include<stack>
#include<queue>
#define ll long long
//#define int 1e9
using namespace std;
int dpmax[509][509] = {0};
int dpmin[509][509] = {0};
int a[509] = {0};
int sum[509] = {0};
int main(){
int n;
scanf("%d",&n);
for(int i = 1;i<=n;i++){
scanf("%d",&a[i]);
a[i+n] = a[i];
}
for(int i = 1;i<=2*n;i++){
for(int j = 1;j<=2*n;j++){
dpmin[i][j] = 1e9;
}
}
for(int i = 1;i<=2*n;i++){
sum[i] = sum[i-1] + a[i];
dpmax[i][i] = dpmin[i][i] = 0;
}
for(int len = 1;len<=n;len++){
for(int j = 1;j+len-1<=2*n;j++){
int ends = j+len-1;
for(int i = j;i<ends;i++){
dpmax[j][ends] = max(dpmax[j][ends],dpmax[j][i]+dpmax[i+1][ends]+sum[ends]-sum[j-1]);
dpmin[j][ends] = min(dpmin[j][ends],dpmin[j][i]+dpmin[i+1][ends]+sum[ends]-sum[j-1]);
}
}
}
int maxx = -1;
int minn = 1e9;
for(int i = 1;i<=n;i++){
minn = min(minn,dpmin[i][i+n-1]);
maxx = max(maxx,dpmax[i][i+n-1]);
}
printf("%d\n%d",minn,maxx);
return 0;
}
时间优化
这类问题可以优化到o(n^2),
用到一种优化方法:四边形不等式
对于状态转移式为:dp[i] [j] = min(dp[i] [j],dp[i] [k]+dp[k+1] [j]+w[i] [j])。
满足以下条件时,可以优化为(o^2);
(1) 四边形不等式 : w[a] [c] + w[b] [d]<=w[a] [d] + w[b] [c] (a<=b<=c<=d) (小口诀,包含大于等于交叉)
(2)w关于区间包含关系单调:
设s[i] [j]是dp[i] [j]取最优时的k值
当求最小值时:
k的范围就限制在了 s[i] [j-1]<=k<=s[i+1] [j]。这样时间复杂度就控制在了(o^2)
求最大值时:
状态转移方程就变成了dp[i] [j] = max(dp[i] [j-1],dp[i+1] [j])。时间复杂度还是(o^2)
#include<cstdio>
#include<cstring>
#include<cmath>
#include<cstdlib>
#include<iostream>
#include<algorithm>
#include<vector>
#include<set>
#include<map>
#include<stack>
#include<queue>
#define ll long long
//#define int 1e9
using namespace std;
int dp[2005][2005];
int sum[2005];
int s[2005][2005];
int num[2005];
int main()
{
int n;
scanf("%d",&n);
memset(sum,0,sizeof(sum));
memset(dp,0x3f,sizeof(dp));
for(int i = 1;i<=n;i++){
scanf("%d",&num[i]);
dp[i][i] = 0;
s[i][i] = i;
sum[i] = sum[i-1] + num[i];
}
for(int i = 1;i<=n;i++){
sum[i+n] = sum[i+n-1] +num[i];
s[i+n][i+n] = i+n;//分割点初始化
dp[i+n][i+n] = 0;
}
for(int len = 1;len<=n;len++){
for(int j = 1;j+len<=2*n;j++){
int ends = j+len - 1;
for(int k = s[j][ends-1];k<=s[j+1][ends];k++){//k的范围
if(dp[j][ends]>dp[j][k]+dp[k+1][ends]+sum[ends]-sum[j-1])
{
dp[j][ends]=dp[j][k]+dp[k+1][ends]+sum[ends]-sum[j-1];
s[j][ends] = k;
}
}
}
}
int ans = 0xfffffff;//一定要开0xfffffff不然错QAQ
for(int i = 1;i<=n;i++){
ans = min(ans,dp[i][i+n-1]);
}
printf("%d\n",ans);
return 0;
}
Brackets
Description
We give the following inductive definition of a “regular brackets” sequence:the empty sequence is a regular brackets sequence,if s is a regular brackets sequence, then (s) and [s] are regular brackets sequences, andif a and b are regular brackets sequences, then ab is a regular brackets sequence.no other sequence is a regular brackets sequenceFor instance, all of the following character sequences are regular brackets sequences:(), [], (()), ()[], ()[()]
while the following character sequences are not:(, ], )(, ([)], ([(]
Given a brackets sequence of characters a1a2 … an, your goal is to find the length of the longest regular brackets sequence that is a subsequence of s. That is, you wish to find the largest m such that for indices i1, i2, …, im where 1 ≤ i1 < i2 < … < im ≤ n, ai1ai2 … aim is a regular brackets sequence.Given the initial sequence ([([]])]
, the longest regular brackets subsequence is [([])]
.
Input
The input test file will contain multiple test cases. Each input test case consists of a single line containing only the characters (
, )
, [
, and ]
; each input test will have length between 1 and 100, inclusive. The end-of-file is marked by a line containing the word “end” and should not be processed.
Output
For each input case, the program should print the length of the longest possible regular brackets subsequence on a single line.
Sample Input
((()))
()()()
([]])
)[)(
([][][)
end
Sample Output
6
6
4
0
6
思路:首先这是一个在区间内求解问题的题目,我们先来看看这题是否为区间dp,首先当我们找到了一个匹配的括号
(即s[i]=’(’&&s[j]=’)‘或者s[i]=’[’&&s[j]=’]’),那么我们很容易的可以得到 dp[i] [j] = d[i+1] [j-1]+2。带入第一个样例好像没错,但是是否就这样就结束了呢,显然并没有第二个样例就wa了,所以在i~j区间,还需要再测一次dp[i] [k] + dp[k] [j],看看哪个大。
ac代码:
#include<cstdio>
#include<cstring>
#include<cmath>
#include<cstdlib>
#include<iostream>
#include<algorithm>
#include<vector>
#include<set>
#include<map>
#include<stack>
#include<queue>
#define ll long long
//#define int 1e9
using namespace std;
char s[10000] = {'\0'};
int dp[5000][5000] = {'\0'};
int main(){
s[0] = ' ';
while(1){
scanf(" %s",s+1);
if(s[1] == 'e') break;
int n = strlen(s) - 1;
for(int i = 1;i<=n;i++){
for(int j = 1;j<=n;j++){
dp[i][j] = 0;
}
}
for(int i = 1;i<=n;i++){
dp[i][i] = 0;
}
for(int len = 2;len<=n;len++){
for(int j = 1;j+len-1<=n;j++){
int ends = j+len-1;
if((s[j] == '('&&s[j+len-1] == ')')||(s[j] == '['&&s[j+len-1] == ']')){
dp[j][ends] = dp[j+1][ends-1] + 2;
}
for(int i = 1;i<ends;i++){
dp[j][ends] = max(dp[j][ends],dp[j][i]+dp[i+1][ends]);
}
}
}
printf("%d\n",dp[1][n]);
}
return 0;
}