区间dp

区间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 < … < imn, 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;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值