动态规划--(递推2(最长上升子序列,格子染色,斐波那切数列,奇数塔问题,最长子段和))

文章介绍了如何利用动态规划方法解决两个经典问题:最长上升子序列和最长子段和。通过定义状态转移方程和递推过程,展示了如何计算给定序列的最长上升子序列长度和连续子段和的最大值。
摘要由CSDN通过智能技术生成

1281:最长上升子序列
【题目描述】
一个数的序列bi
,当b1<b2<…<bS
的时候,我们称这个序列是上升的。对于给定的一个序列(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个整数,这些整数的取值范围都在0到10000。

【输出】
最长上升子序列的长度。

【输入样例】
7
1 7 3 5 9 4 8
【输出样例】
4
【分析】最长上升子序列是动规的经典例题,但还是那句话,先确定dp,再确定状态转移方程,随后找最大值,由于这题是包含递推的,所以状态转移方程和递推有关,但是我们仍要先确定dp。那我们是不是可以设dp[i]表示前i个数的最长上升子序列的长度为dp[i],因为是递推所以dp可以设简单点。然后我们假设一个点为i,那么前i个最长上升子序列是不是max(1+dp[k]),其中的k是小于等于i-1大于等于1的,而且a[k]<a[i],所以这个的意思就是,dp[i]是由前i-1个中的一个数的最长上升子序列+1的最大值得来的,其中那个数必须小于a[i]。这样说还是有点抽象,举个例子,比如我要求前i个数的最长上升子序列,那么小于a[i]的数有a[v1],a[v2],a[v3]这三个,而dp[i]就是dp[v1]+1,dp[v2]+1,dp[v3]+1,dp[i](因为前三个的最大值可能小于dp[i],所以也要加上,但是这是不可能的事,不过为了后面的统一还是加上)的最大值,所以状态转移方程是dp[i]=max(dp[i],dp[k]+1)(k为小于i大于0的自然数)。
【代码环节】代码如下

#include <bits/stdc++.h>
using namespace std;
int n;
int a[1050],dp[1050]; 
int main(){
ios::sync_with_stdio(0);
cin.tie(0);cout.tie(0);
cin>>n;
for(int i=1;i<=n;i++) cin>>a[i];
for(int i=1;i<=n;i++) dp[i]=1;
for(int i=1;i<=n;i++){
	for(int j=1;j<i;j++){
		if(a[i]>a[j]){
			dp[i]=max(dp[i],dp[j]+1);
		}
	}
}
int mx=0;
for(int i=1;i<=n;i++) mx=max(mx,dp[i]);   //注意最长上升子序列可能不是dp[n],要再枚举一遍
cout<<mx;
    return 0;
}

斐波那契数
第1个斐波那契数是1
第2个斐波那契数是1
第3个斐波那契数是2
后面每一个斐波那契数都等于前两个斐波那契数相加
f[n]=(1)(n<=2) ---- (f[n-1]+f[n-2])(n>2)
求第n个斐波那契数
输入
输入一个整数
n≤30
输出
输出一个 整数

样例
输入 1
6
输出 1
8

【分析】等等…这题还要分析?直接通通一顿递归

int fun(int n){
	if(n<=2) return 1;
	else return fun(n-1)+fun(n-2);
} 

而递归的时间复杂度是O(t(n)=o(f(n)))但是,这样你会发现,当数大时就不行,比如50,这该怎么办呢?或许可以用dp,不过最主要的是题目已经把dp给你设出来了,dp[i]表示第i个斐波那锲数列的值,状态转移方程也给了 f[n]=f[n-1]+f[n-2],所以此题可用dp,那时,时间复杂度将会是O(n)

#include <bits/stdc++.h>
using namespace std;
int f[55];
int main(){
ios::sync_with_stdio(0);
cin.tie(0);cout.tie(0);
int n;
cin>>n;
f[1]=1,f[2]=1;
for(int i=3;i<=n;i++){
	f[i]=f[i-1]+f[i-2];
}
cout<<f[n];
    return 0;
}

格子染色
有排成一行的n个方格,用红(Red)、粉(Pink)、绿(Green)三色涂每个格子,每格涂一色,要求任何相邻的方格不能同色,且首尾两格也不同色。
求全部的满足要求的涂法种数。
输入
输入一个整数n(1≤n≤50)。
输出
输出一个整数表示答案。

样例
输入 1
1
输出 1
3
输入 2
2
输出 2
6
【分析】这题其实是一道数学题,不知道大家知不知道传球法不过这里也不细说了,大家可以上网查查。传球法的过程其实就是dp的递推,所以代码如下

#include<bits/stdc++.h>
using namespace std;
long long dp[55][3];
int main()
{
int n;
cin>>n;
if(n==1){
	cout<<3;
	return 0;
}
dp[1][0]=0;
dp[1][1]=dp[1][2]=1;
for(int i=2;i<=n;i++)
{
	dp[i][0]=dp[i-1][1]+dp[i-1][2];//每个球都是起他俩个传来的
	dp[i][1]=dp[i-1][0]+dp[i-1][2];
	dp[i][2]=dp[i-1][1]+dp[i-1][0];
}
cout<<dp[n][0]*3;//起点有三种,我们只算了一种,所以乘3
	return 0;
} 

拓展【公式法】环形染色公式ans=(m-1)^n+ (-1)^n*(m-1) m为种类,n为长度
推导可上网查阅。

#include<bits/stdc++.h>
using namespace std;
long long n;
int main()
{
cin>>n;
if(n==1){  //特判
	cout<<3;
	return 0;
}
long long p=pow(2,n)+pow(-1,n)*2;//公式
cout<<p;
	return 0;
} 

奇数塔问题
之前,我们已经完成了经典的数塔问题,现在我们将其升级成奇数塔问题,它是这样描述的:

有如下所示的数塔,要求从顶层走到底层,若每一步只能走到相邻的结点,最终答案必须在最后一行,且为奇数,问答案最大是多少?

如果没有答案,则输出-1。

数塔

输入
第一行是一个整数N(1 <= N <= 100),表示数塔的高度,接下来用N行数字表示数塔,其中第i行有个i个整数,且所有的整数均在区间[0,100]内。

输出
对于每个测试实例,输出可能得到的最大和,如果没有答案,则输出-1。每个实例的输出占一行。

样例
输入 1
5
7
3 8
8 1 0
2 7 4 4
4 5 2 6 5
输出 1
27
输入 2
2
3
5 11
输出 2
-1
提示
1 <= N <= 100 1 <= 数塔中的数 <= 100
【分析】其实这题可以分俩种状态,一个是可以达成奇数,另一个是不可以,我们可以标为-1,还有就是奇数+偶数=奇数,另外和数塔问题相差不大,不会数塔问题的可以看
1258:【例9.2】数字金字塔-----动态规划(递推)1
代码如下

#include<bits/stdc++.h>
using namespace std;
int dp[110][110][2];
int a[110][110];
int main()
{
memset(dp,-1,sizeof(dp));
int n;
scanf("%d",&n);
for(int i=1;i<=n;i++){
	for(int j=1;j<=i;j++){
		scanf("%d",&a[i][j]);
	}
}
if(a[1][1]%2==1){
	dp[1][1][1]=a[1][1];
}
else{
	dp[1][1][0]=a[1][1];
}
for(int i=2;i<=n;i++){
	for(int j=1;j<=i;j++){
		if(a[i][j]%2==1){
			if(dp[i-1][j][0]!=-1||dp[i-1][j-1][0]!=-1)
			   dp[i][j][1]=max(dp[i-1][j][0],dp[i-1][j-1][0])+a[i][j];
			if(dp[i-1][j][1]!=-1||dp[i-1][j-1][1]!=-1)
			   dp[i][j][0]=max(dp[i-1][j][1],dp[i-1][j-1][1])+a[i][j];
		}
		else{
			if(dp[i-1][j][1]!=-1||dp[i-1][j-1][1]!=-1)
			   dp[i][j][1]=max(dp[i-1][j][1],dp[i-1][j-1][1])+a[i][j];
			if(dp[i-1][j][0]!=-1||dp[i-1][j-1][0]!=-1)
			   dp[i][j][0]=max(dp[i-1][j][0],dp[i-1][j-1][0])+a[i][j];
		}
	}
}
int ret=-1;
for(int i=1;i<=n;i++){
	if(dp[n][i][1]>ret){
	ret=dp[n][i][1];
	}
}
printf("%d",ret);
	return 0;
} 

最长子段和
给出一个长度为n 的序列 a,选出其中连续且非空的一段使得这段和最大。

如果均为负数,则输出0。

输入
第一行是一个整数n(1≤n≤2⋅10 ^5 )。

第二行有n 个整数,第 i 个整数表示序列的第 i 个数字

(−10 ^4 ≤a i≤10 ^4 )​。
输出
输出一个整数表示答案。
样例
输入 1
7
2 -4 3 -1 2 -4 3
输出 1
4
【分析】这题我们可以假设dp[i]表示前i个数的最大子段和,还是那句话,递推的dp一般不会太复杂。想想我们该怎么得到dp[i]呢?其实有俩种,1:前i-1的最大子段和加上当前的,构成子段和。2:当前的数单独成为字段。最后求他俩最大值。用状态转移方程的话讲:max(a[i]+dp[i-1],a[i])
代码如下

#include<bits/stdc++.h>
using namespace std;
const int N=2e5+5;
int n,a,dp[N];
int main()
{
dp[0]=0;
scanf("%d",&n);
for(int i=1;i<=n;i++){
	scanf("%d",&a);
	dp[i]=max(a+dp[i-1],a);  //求自身和上一个加自身谁大
}
int mx=0;
for(int i=1;i<=n;i++) mx=max(mx,dp[i]);//找最大值
printf("%d",mx);
	return 0;
} 
  • 24
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值