Codeup动态规划问题

本文介绍了使用动态规划解决一系列问题,包括Fibonacci数列、求解最大子列和、最长不下降子序列(LIS)以及最长公共子序列(LCS)的问题。每个问题都详细阐述了题目描述、输入输出格式,并给出了优化的解题思路。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

11.1

问题 A: Fibonacci

[命题人 : 外部导入]
时间限制 : 1.000 sec 内存限制 : 32 MB

题目描述
The Fibonacci Numbers{0,1,1,2,3,5,8,13,21,34,55…} are defined by the recurrence:
F0=0 F1=1 Fn=Fn-1+Fn-2,n>=2
Write a program to calculate the Fibonacci Numbers.

输入
Each case contains a number n and you are expected to calculate Fn.(0<=n<=30) 。

输出
For each case, print a number Fn on a separate line,which means the nth Fibonacci Number.

样例输入 Copy
1
样例输出 Copy
1

#include<stdio.h>

#define maxn 50

int dp[maxn];
int func(int n);
int main()
{
	int n;
	
	while(scanf("%d",&n)!=EOF){
		for(int i=0;i<=n;i++)
		dp[i]=-1;
	
	int fib=func(n);
	printf("%d\n",fib);
	}
		
	return 0;
}

int func(int n)
{
	if(n==0)	return 0;
	if(n==1)	return 1;
	if(dp[n]!=-1)	return dp[n];
	else{
		dp[n]=func(n-1)+func(n-2);
		return dp[n];
	}		
	
}

用dp数组来记录已经计算过的数字,如果没有计算过,那么dp[n]=func(n-1)+func(n-2);
另外输入的时候必须用while…!=EOF,不然答案通不过

11.2最大子列和

#include<stdio.h>
#include<algorithm>
#define maxn 10010
using namespace std;

int a[maxn],dp[maxn];
int main()
{
	int n;
	scanf("%d",&n);
	
	for(int i=0;i<n;i++){
		scanf("%d",&a[i]);
	}
	
	dp[0]=a[0];
	for(int i=0;i<n;i++){
		dp[i]=max(a[i],dp[i-1]+a[i]);//状态转移方程
	}
	
	int k=0;
	for(int i=0;i<n;i++){
		if(dp[i]>dp[k])	k=i;
	}
	
	printf("%d\n",dp[k]);
	
	
	return 0;
}

问题 A: 最大连续子序列

[命题人 : 外部导入]
时间限制 : 1.000 sec 内存限制 : 32 MB

题目描述
给定K个整数的序列{ N1, N2, …, NK },其任意连续子序列可表示为{ Ni, Ni+1, …, Nj },其中 1 <= i <= j <= K。最大连续子序列是所有连续子序列中元素和最大的一个,例如给定序列{ -2, 11, -4, 13, -5, -2 },其最大连续子序列为{ 11, -4, 13 },最大和为20。现在增加一个要求,即还需要输出该子序列的第一个和最后一个元素。

输入
测试输入包含若干测试用例,每个测试用例占2行,第1行给出正整数K( K<= 10000 ),第2行给出K个整数,中间用空格分隔,每个数的绝对值不超过100。当K为0时,输入结束,该用例不被处理。

输出
对每个测试用例,在1行里输出最大和、最大连续子序列的第一个和最后一个元素,中间用空格分隔。如果最大连续子序列不唯一,则输出序号i和j最小的那个(如输入样例的第2、3组)。若所有K个元素都是负数,则定义其最大和为0,输出整个序列的首尾元素。

样例输入 Copy
5
-3 9 -2 5 -4
3
-2 -3 -1
0
样例输出 Copy
12 9 5
0 -2 -1
提示
这是一道稍微有点难度的动态规划题。

首先可以想到的做法是枚举每个区间的和,预处理sum[i]来表示区间[1, i]的和之后通过减法我们可以O(1)时间获得区间[i, j]的和,因此这个做法的时间复杂度为O(n^2)。

然后这题的数据范围较大,因此还需作进一步优化才可以AC。记第i个元素为a[i],定义dp[i]表示以下标i结尾的区间的最大和,那么dp[i]的计算有2种选择,一种是含有a[i-1],一种是不含有a[i-1],前者的最大值为dp[i-1]+a[i],后者的最大值为a[i]。而两者取舍的区别在于dp[i-1]是否大于0。

#include<stdio.h>
#include<algorithm>
#define maxn 10010

using namespace std;

int a[maxn],dp[maxn],cnt[maxn]={0};
//cnt记录以a[i]为结尾的最长子列和中元素个数
int main()
{
	int k;
	while(scanf("%d",&k)!=EOF){
		if(!k)	break;
		for(int i=0;i<k;i++){
			scanf("%d",&a[i]);
		}
		int i;
		for(i=0;i<k;i++){
			if(a[i]>=0)	break;
		}
		if(i==k){
			printf("0 %d %d\n",a[0],a[k-1]);
		}
		else{
			dp[0]=a[0];
			cnt[0]=1;
			for(int i=1;i<k;i++){
				dp[i]=max(a[i],dp[i-1]+a[i]);
				if(dp[i]==a[i])	cnt[i]=1;
				else cnt[i]=cnt[i-1]+1;
			}
			int tmp=0;
			for(int i=0;i<k;i++){
				if(dp[i]>dp[tmp])	tmp=i;
			}
			printf("%d %d %d\n",dp[tmp],a[tmp-cnt[tmp]+1],a[tmp]);
			
		}	
	}

	return 0;
}

11.3最长不下降子序列(LIS)

#include<stdio.h>
#include<algorithm>
#define N 100
using namespace std;

int dp[N],a[N];

int main()
{
	int n;
	scanf("%d",&n);
	for(int i=0;i<n;i++){
		scanf("%d",&a[i]);
	}
	int ans=-1;//记录最大的dp[i] 
	for(int i=0;i<n;i++){
		dp[i]=1;//边界条件
		for(int j=0;j<i;j++){
			if(a[j]<=a[i] && (dp[j]+1>dp[i]))
				dp[i]=dp[j]+1;
		} 
		ans=max(ans,dp[i]);
	}
	printf("%d",ans);
	
	return 0;
}

问题 A: 最长上升子序列

[命题人 : 外部导入]
时间限制 : 2.000 sec 内存限制 : 64 MB

题目描述
一个数列ai如果满足条件a1 < a2 < … < aN,那么它是一个有序的上升数列。我们取数列(a1, a2, …, aN)的任一子序列(ai1, ai2, …, aiK)使得1 <= i1 < i2 < … < iK <= N。例如,数列(1, 7, 3, 5, 9, 4, 8)的有序上升子序列,像(1, 7), (3, 4, 8)和许多其他的子序列。在所有的子序列中,最长的上升子序列的长度是4,如(1, 3, 5, 8)。

现在你要写一个程序,从给出的数列中找到它的最长上升子序列。

输入
输入包含两行,第一行只有一个整数N(1 <= N <= 1000),表示数列的长度。

第二行有N个自然数ai,0 <= ai <= 10000,两个数之间用空格隔开。

输出
输出只有一行,包含一个整数,表示最长上升子序列的长度。

样例输入 Copy
7
1 7 3 5 9 4 8
样例输出 Copy
4

#include <stdio.h>
#include<algorithm>
#define N 1010

using namespace std;

int dp[N],a[N];
int main()
{
	int n;
	scanf("%d",&n);
	
	for(int i=0;i<n;i++){
		scanf("%d",&a[i]);
	}
	
	int ans=-1;
	for(int i=0;i<n;i++){
		dp[i]=1;
		for(int j=0;j<i;j++){
			if(a[j]<a[i] && (dp[j]+1>dp[i]))
				dp[i]=dp[j]+1;
		}
		ans=max(ans,dp[i]);
	}
	printf("%d\n",ans);
	
	return 0;
}

11.4最长公共子序列(LCS)

状态转移方程:
if(a[i]==b[j]) dp[i][j]=dp[i-1][j-1]+1;
else dp[i][j]=max(dp[i-1][j],dp[i][j-1]);

#include<stdio.h>
#include<string.h>
#include<algorithm>
#define N 100
using namespace std;
 
char a[N],b[N];
int dp[N][N];
int main()
{
	gets(a+1);
	gets(b+1);
	int len1=strlen(a+1);
	int len2=strlen(b+1);
	
	for(int i=0;i<=len1;i++)	dp[i][0]=0;
	for(int i=0;i<=len2;i++)	dp[0][i]=0;
	
	for(int i=1;i<=len1;i++){
		for(int j=1;j<=len2;j++){
			if(a[i]==b[j])	dp[i][j]=dp[i-1][j-1]+1;
			else	dp[i][j]=max(dp[i-1][j],dp[i][j-1]);
		}
	}
	
	printf("%d\n",dp[len1][len2]);
	
	return 0;
}

问题 A: 最长公共子序列

[命题人 : 外部导入]
时间限制 : 1.000 sec 内存限制 : 32 MB

题目描述
给你一个序列X和另一个序列Z,当Z中的所有元素都在X中存在,并且在X中的下标顺序是严格递增的,那么就把Z叫做X的子序列。
例如:Z=<a,b,f,c>是序列X=<a,b,c,f,b,c>的一个子序列,Z中的元素在X中的下标序列为<1,2,4,6>。
现给你两个序列X和Y,请问它们的最长公共子序列的长度是多少?
输入
输入包含多组测试数据。每组输入占一行,为两个字符串,由若干个空格分隔。每个字符串的长度不超过100。
输出
对于每组输入,输出两个字符串的最长公共子序列的长度。
样例输入 Copy
abcfbc abfcab
programming contest
abcd mnp
样例输出 Copy
4
2
0

#include <stdio.h>
#include <string.h>
#include <algorithm>

using namespace std;

#define N 110

char a[N],b[N];
int dp[N][N]={0};

int main()
{
	while(scanf("%s %s",a+1,b+1)!=EOF){
		int len1=strlen(a+1);
		int len2=strlen(b+1);

		for(int i=1;i<=len1;i++){
			for(int j=1;j<=len2;j++){
				if(a[i]==b[j])	dp[i][j]=dp[i-1][j-1]+1;
				else dp[i][j]=max(dp[i-1][j],dp[i][j-1]);
			}
		}
		 
		printf("%d\n",dp[len1][len2]);
	
	}
	 
	return 0;
}	

11.5最长回文子串

#include<stdio.h>
#include<string.h>
#include<algorithm>
#define maxn 1010
using namespace std; 
char s[maxn];
int dp[maxn][maxn];
int main()
{
	gets(s);
	int len=strlen(s);
	int ans=1;
	memset(dp,0,sizeof(dp));
	//边界 
	for(int i=0;i<len;i++){
		dp[i][i]=1;
		if(i<len-1){
			if(s[i]==s[i+1]){
				dp[i][i+1]=1;
				ans=2;//初始化注意当前最长回文子串长度 
			}
		}
	}
	//状态转移方程
	for(int l=3;l<=len;l++){
		for(int i=0;i+l<len;i++){//枚举子串起始端点 
			int j=i+l-1;//子串右端点 
			if(s[i]==s[j] && dp[i+1][j-1]==1){
				dp[i][j]=1;
				ans=l;//更新最长回文子串长度 
			}
		}
	} 
	printf("%d\n",ans);
	return 0; 
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值