动态规划:线性dp、背包问题、区间3

区间DP

2955 -- Brackets

给定一个由字符 a1a2 ... an 组成的括号序列,你的目标是找到最长的正则括号序列的长度,它是 s 的子序列。也就是说,您希望找到最大的 m,使得对于索引 i1、i2、...、im,其中 1 ≤ i1 < i2 < ... < im ≤ n,ai1ai2 ... aim 是常规括号序列。

接下来就是强行把自己讲懂的……QAQ

子状态:我们想,既然最后要求的是从0到len-1的区间中有多少个括号被匹配,那这个答案是怎么推过来的呢?肯定是从它的前面推过来的,也就是0到len-2等等,那就设子状态为f[i][j],其中i表示起始位置,j表示终止位置。

转移方程:每一个区间都是它由括号划分成的  两段子区间之和  中的最大值,                         即 f[i][j]=max(f[i][j],f[i+1][k-1]+f[k+1][j]+2),如果没有匹配的括号,说明可以略过第一个括号,     即f[i][j]=f[i+1][j]。

边界条件:若i>=j,则f[i][j]=0。当f[i][j]有值就直接返回,所以初始值应当设为-1。

#include
#include
#include
#include
using namespace std;
typedef long long ll;
//scanf("",&);
const int inf = 0x3f3f3f, maxn=105;
string s;
int f[maxn][maxn];
int calc(int i,int j){
	if(f[i][j]!=-1)
		return f[i][j];
	if(i>=j)
		return f[i][j]=0;
	f[i][j]=calc(i+1,j);
	for(int k=i+1;k<=j;++k)//
		if((s[i]=='('&&s[k]==')')||(s[i]=='['&&s[k]==']'))
			f[i][j]=max(f[i][j],calc(i+1,k-1)+calc(k+1,j)+2);
	return f[i][j];
}
int main(){
	while(cin>>s){
		if(s=="end") break;
		memset(f,-1,sizeof(f));
		int len=s.length();
		calc(0,len-1);
		cout<

最长回文子序列长度

给你一个长度为n的序列,求最长回文子序列长度(子序列可不连续)。

法一:线性dp。可以求原串和其倒序串的最长公共子序列。

法二:区间dp。f[i][j]表示ij之间的最长回文子序列,则若ai与aj相同,f[i][j]=f[i+1][j-1]+2,否则          f[i][j]=max(f[i+1][j], f[i][j-1]),即分别求去掉头尾的最长回文子序列。

拓展:最长回文子串长度(子串必须连续)

既然一个串的子串只要有一个不满足回文就无法再扩展回文长度,那么我们可以直接设置bool数组,f[i][j]表示从i到j是否为回文串,则f[i][j]=f[i+1][j-1]&&(a[i]==a[j])。答案用ans=max(ans,j-i+1)维护即可。更优的办法:manacher。

链接:登录—专业IT笔试面试备考平台_牛客网
来源:牛客网
 

石子合并

将n堆石子绕圆形操场排放,现要将石子有序地合并成一堆。规定每次只能选相邻的两堆合并成新的一堆,并将新的一堆的石子数记做该次合并的得分。
请编写一个程序,读入堆数n及每堆的石子数,并进行如下计算:

选择一种合并石子的方案,使得做n-1次合并得分总和最大。

选择一种合并石子的方案,使得做n-1次合并得分总和最小。

先想没环的情况,避免把自己绕进去。

考虑最终状态:相当于将前若干石子和后若干石子的堆再合并。即相当于前i个合并后的最大值和后j个合并后的最大值,与最后这一堆的代价之和。则f[i][j]表示合并i到j所有区间的得分,即               f[i][j]=max(f[i][j], f[i][k]+f[k+1][j]+sum[i][j]),sum[i][j]表示前i个式子的价值和(也可用前缀和维护)。

有环的情况:可以将区间加倍,变为‘12341234’,通过依次枚举其中的区间来达到循环的目的。

细节问题:枚举时应当先确定区间长度,后才是区间左端点和右端点;无论是对记忆化搜索还是枚举,都应当在每次枚举的区间操作前赋初始值。

#include
#include
#include
#include
#include
#include
using namespace std;
typedef long long ll;
//scanf("",&);
const int inf = 0x3f3f3f, maxn=405;
string s;
int n,a[maxn];
ll sum[maxn],mx[maxn][maxn],mi[maxn][maxn],ans;
//int calc1(int i,int j){
//	if(mx[i][j]!=-1) 
//		return mx[i][j];
//	if(i>=j)
//		return 0;
//	for(int k=i;k=j)
//		return 0;
//	mi[i][j]=inf;//注意,赋初值在这里赋 
//	for(int k=i;k>n;
//	memset(mx,-1,sizeof(mx));
	for(int i=1;i<=n;++i){
		cin>>a[i];
		a[i+n]=a[i];
	}
	for(int i=1;i<=2*n;++i)
		sum[i]=a[i]+sum[i-1];
	
//	ans=inf;
//	calc2(1,2*n);
//	for(int i=1;i<=n;++i)
//		ans=min(ans,mi[i][i+n-1]);
//	cout<

凸多边形最优三角剖分

给定凸多边形P,以及定义在由多边形的边和弦组成的三角形上的权函数w。要求确定该凸多边形的三角剖分,使得即该三角剖分中诸三角形上权之和为最小。

凸多边形的剖分总有开始。假设凸多边形为v0-v7,但若以其中一点作为起始点进行三角形的枚举会导致分为多个多边形,难以处理情况。

考虑为三角形的拆分制定规范,即固定其中一个量枚举其他点。如固定v0与v7的边作为最后一次去切多边形的边,则最多将多边形分成三个部分且三个部分满足v0~vk,vk~v7,即左半部分总包含v0但右半部分始终不包含v0(除非最后分为一个三角形和一个多边形),而剖分成的多边形内部继续如此分割,始终保证序号不相邻的两点构成的边为基础进行剖分。

设f[i][j](i

#include
#include
#include
#include
#include
#include
using namespace std;
typedef long long ll;
//scanf("",&);
const int inf = 0x3f3f3f, maxn=405;
int n,s[maxn],f[maxn][maxn],ans;
int calc(int i,int j){
	if(f[i][j]!=-1) 
		return f[i][j];
	if(i+1>=j) 
		return 0;
	for(int k=i+1;k>n;
	for(int i=0;i>s[i];
	memset(f,-1,sizeof(f));
	cout<

田忌赛马

如果不止三匹马怎么办?这个问题很显然可以转化成一个二分图最佳匹配的问题。把田忌的马放左边,把齐王的马放右边。田忌的马A和齐王的B之间,如果田忌的马胜,则连一条权为200的边;如果平局,则连一条权为0的边;如果输,则连一条权为-200的边……如果你不会求最佳匹配,用最小费用最大流也可以啊。 然而,赛马问题是一种特殊的二分图最佳匹配的问题,上面的算法过于先进了,简直是杀鸡用牛刀。现在,就请你设计一个简单的算法解决这个问题。 

齐王的马总是按照从高到低出,赢了得钱,输了赔钱,平局不赢不赔。田忌最强的马如果能赢齐王的马,就去赢;但如果赢不了,就应该让最菜的马上场。不需要拿赢面最小的马去赢,因为田剩下的能赢齐王的马,同样也能赢后面齐王的马。这时候看起来像一个贪心。

但如果战平怎么办?反例出现了。假设田忌1 2 3,齐王1 2 3,这时候可以用最菜的马故意输;但田忌2 3,齐王1 3,就不如先战平。所以能战平的时候就取决于剩下的马该怎么办了,不能用贪心了。但显然,田忌无论如何一定要拿最强或最弱的马去比。这时候最好的情况需要用动态规划去求。

由于我们不知道出战的是哪一匹马,我们把状态定义为f[k][i][j]表示后k轮比赛中,田忌已经使用了第i到j匹马时能获得的最多钱数,田忌出的要么是第一匹要么是最后一匹,而齐王出的是第k匹马。即:

f[k][i][j]=max\left\{\begin{matrix}KvsI+f[k+1][i+1][j] \\ KvsJ+f[k+1][i][j-1] \end{matrix}\right.

边界:f[n][i][i],即齐王第n匹马与田忌第i匹马比赛结果。

又因为j-i+1=n-k+1即田忌和齐王还剩的马数量相同,所以在求解过程中k那一维可以不要。

为什么要从后k轮推导?因为前面的轮数在区间的两头摇摆不定,无法保证区间连续,而且要用后k轮决定田忌当前出的马是最弱的还是最强的,因为由上面的反例可以看出,当前若出现平局的情况,需要由后k轮的最优结果来决定出哪一匹马。

#include
#include
#include
#include
#include
#include
using namespace std;
typedef long long ll;
//scanf("",&);
const int inf = 0x3f3f3f, maxn=2005;
int n,t[maxn],q[maxn],f[maxn][maxn];
int jg(int i,int j){
	if(t[i]>n;
	for(int i=1;i<=n;++i)
		scanf("%d",&t[i]);
	for(int i=1;i<=n;++i)
		scanf("%d",&q[i]);
	//有负权的情况下还是用枚举
	sort(t+1,t+1+n);
	sort(q+1,q+1+n);
	for(int i=1;i<=n;++i)
		f[i][i]=jg(i,1);
	for(int k=2;k<=n;++k)//区间长度 ,即倒数第几轮比赛,所以顺序从小到大 
		for(int i=1;i<=n-k+1;++i)//区间起点 
			f[i][i+k-1]=max(f[i+1][i+k-1]+jg(i,k),f[i][i+k-2]+jg(i+k-1,k));
	cout<<200ll*f[1][n];
	return 0;
}

最大的两个不重叠子串和

给你一个长度为n的数列,找到其中两个不重合的子串,使得两串之和最大。

枚举两串的分界点,令两个区间分别为[a,b]和[c,d],假设枚举右子串的第一个元素c。[a,b]对应原序列前c个数的最大子串和。对应到原来的最大子串和,f[i]代表必须选第i个数的最大子串和,现在题目中的b相当于f[i]中的i,但此时的b可能是小于c的任何一个值,即b只需取到f数组的前缀最大值。

定义g[i]为前i个数组的最大子串和,则当g[i]=max(f[i],g[i-1]),f[i]代表必须选第i个数,g[i-1]代表第i个数可选可不选,则[a,b]即可表示为g[c-1]。[c,d]是后c个元素最大子串和,其中第c个元素必须要选。此时只需要将上述过程从右向左反着做一遍得到另外一个ff数组,表示后i个数且第i个数一定要选的最大子串和。

由此我们可分别将f、g、ff求出来,后枚举两个串的分界点c,后将g[c-1]与ff[c]相加即为答案。

多个不相交子串和问题

给定一个长度为n的数组,求在其中找m个不相交子串和的最大值。

对于每一个数,存在要和不要两种情况。对于要的数,分为将其接在上个不相交子串的后面,以及将它当做新一个不相交子串的起点。可以发现不要此数的状态可以先不管,因为不要的话后面的元素没办法直接接上来。求单个字串和时,第i个元素一定要选,现在多了一个限制:子串个数m个。通过背包问题我们可以想到应当多加一维数组表示条件限制。

则定义如下数组:f[i][j],表示前i个数(第i个数必须取)组成j个不相交子段所能取得的最大和,    f[i][j]=max(f[i-1][j]+a[i], f[k][j-1]+a[i]),f[i-1][j]+a[i]表示接在上个子串的后面,f[k][j-1]+a[i]为作为新子串的开端,其中k表示起始位置,即上一个数的位置可能是前面所有的数,枚举范围为0~i-1,所以总时间复杂度为O(mn^2),需要优化为O(nm),即不能枚举k。

观察式子,暂且把a[i]提出来不管,可以发现当前值取决于其上一行的数与上一行、左一列之前行中的最大值。如图所示:

动态规划:线性dp、背包问题、区间3_第1张图片

1. 我们可以想到维护一个数组maxx[i][j]表示在第j列中,第i行及其之前行中所有值的最大值。这样,状态转移的式子就变成:f[i][j]=max(f[i-1][j],maxx[i-1][j-1])+a[i]

2.发现第i个数的选择有4种可能:第i个数有选和不选两种可能,选的情况下有作为一个新子串起点和接在上一个子串后面两种办法。f[i][j][0/1]表示前i个数已经选了j个子串,第i个数选(0)、不选(1)时的最大子串和。

f[i][j][0]=max(f[i-1][j][0], 		//不选第i-1个数 
			   f[i-1][j][1]);		//与选第i-1个数组成的最大子串和	    //不选第i个数
f[i][j][1]=max(f[i-1][j][1]+a[i],	//作为前一个子串的末尾 
			   f[i-1][j-1][0]+a[i],	//作为与前一个子串不相邻的新子串开端 
			   f[i-1][j-1][1]+a[i]);//作为与前一个子串相邻的新子串开端		//选第i个数

可见f[i][j][1]的后两行可以看做上一种方法的maxx[i][j],都代表了前i个数可选可不选。

拓展:如果是环状的怎么做?

不能像前面石子那样,有很大概率会导致元素使用重复。石子合并是将考虑有环这件事在动态规划之后去做,最后处理的时候枚举了环的断点位置,但通过枚举环的断点位置可以求出答案。

对于这道题我们可以枚举断点,但有一件麻烦的事:只要断开就要重新求一个f数组。现在强行让这个环在1与n之间断开,这之后子串和分两种情况:一,没有任何被断点切分的区间;二,包含断点的区间。

动态规划:线性dp、背包问题、区间3_第2张图片

实际上可以将这种情况转化为:挑m+1(原来是m个)子串,其中第一个必选包含第一个元素,最后一个必须包含最后一个元素。定义f[i][j][0/1]表示前i个元素选了j个子串,第i个元素要选 (1)或不选(0)。当第i个元素要选且已经组成1个区间时,明显第1个元素到第i个元素要全部选上,即f[i][1][1]=sum[i]-sum[0],sum为前缀和。f[i][1][0]=max(f[i-1][1][1], f[i-1][1][0]),后面的与前面做法相同。最终答案为f[n][m+1][1],前n个元素选了m+1个区间,且因为这样才能保证有区间覆盖断点

以上是选到的区间覆盖分界点的情况,再将没覆盖的(其实就是和没环一样)与这个的最大值比较,最后输出即可。这也就是传说中的第二种求解环问题的方法

你可能感兴趣的:(算法学习,算法,动态规划)