week10-作业题-动态规划

A- 签到题

题意

东东在玩游戏“Game23”。

在一开始他有一个数字n,他的目标是把它转换成m,在每一步操作中,他可以将n乘以2或乘以3,他可以进行任意次操作。输出将n转换成m的操作次数,如果转换不了输出-1。

思路

因为n可以乘2或3,每一步操作都是尝试进行的,不行则回溯,所以用dfs进行搜索,到n>=m的时候进行判断是否n==m,相同则输出,不同则回溯。没有则输出-1

总结

对于具有多个操作,每个操作有几种选择,可以用dfs进行搜索,确定符合条件的方案

代码

#include<iostream>
using namespace std;
	long long n,m;
void dfs(int step)
{
	if(n>=m)
	{
		if(n==m)
		{
			cout<<step<<endl;exit(0);
		}
		return;
	}
	for(int i=2;i<=3;i++)
	{
		n=n*i;dfs(step+1);n=n/i;
	}
}
int main()
{

	cin>>n>>m;dfs(0);cout<<"-1"<<endl;return 0;
	
 } 

B - LIS & LCS

题意

东东有两个序列A和B。

他想要知道序列A的LIS和序列AB的LCS的长度。

注意,LIS为严格递增的,即a1<a2<…<ak(ai<=1,000,000,000)。

输入

第一行两个数n,m(1<=n<=5,000,1<=m<=5,000)
第二行n个数,表示序列A
第三行m个数,表示序列B

输出

输出一行数据ans1和ans2,分别代表序列A的LIS和序列AB的LCS的长度

思路

LIS即最长子序列长度,则状态:定义 f[i]表示以 a[i] 为结尾的最长上升序列的长度。
初始化:f[1]=1
转移过程f[i]=max{f[j] | j<i&&a[j]<a[i]}+1
输出答案:max{f[i], i=1…n}
时间复杂度:O(n^2)
LCS即最长公共子序列
设计状态:假设 f[i][j] 为 A1, A2, …, Ai 和 B1, B2, …, Bj 的 LCS 长度
初始化:初始 f[i][0] = f[0][i] = 0
转移方程:当 Ai == Bj 时,f[i][j] = f[i-1][j-1] + 1
否则 f[i][j] = max(f[i-1][j], f[i][j-1])
输出答案:f[n][m]
时间复杂度:O(nm)

总结

这是动态规划的典型例题,就是对每个子问题只求一次,并将其结果保存在一张表中(数组),以后再用到时直接从表中拿过来使用,避免重复计算相同的子问题。
通过合理“组合” 子问题的解 从而解决 整个问题解的一种算法。其中的子问题并不是独立的,这些子问题又包含有公共的子问题。

代码

#include<iostream>
#include<cstring>
using namespace std;
long long a[5010],b[5010];
int f1[5010];
int f2[5010][5010];
int main()
{
	int n,m;cin>>n>>m;
	for(int i=1;i<=n;i++)
	cin>>a[i];
	for(int i=1;i<=m;i++)
	cin>>b[i];
	f1[1]=1;int fmmax=1;
	for(int i=2;i<=n;i++)
	{
		int mmax=0;
		for(int j=1;j<i;j++)
		{
			if(a[j]<a[i])
			mmax=max(mmax,f1[j]);
		}f1[i]=mmax+1;fmmax=max(fmmax,f1[i]);
	}
	memset(f2,0,sizeof(f2));
	for(int i=1;i<=n;i++)
	{
		for(int j=1;j<=m;j++)
		{
			if(a[i]==b[j])
			f2[i][j]=f2[i-1][j-1]+1;
			else f2[i][j]=max(f2[i][j-1],f2[i-1][j]);
		}
	}

	
	cout<<fmmax<<" "<<f2[n][m]<<endl;return 0;
	
 } 

C - 拿数问题 II

题意

给一个序列,里边有 n 个数,每一步能拿走一个数,比如拿第 i 个数, Ai = x,得到相应的分数 x,但拿掉这个 Ai 后,x+1 和 x-1 (如果有 Aj = x+1 或 Aj = x-1 存在) 就会变得不可拿(但是有 Aj = x 的话可以继续拿这个 x)。求最大分数。

输入

第一行包含一个整数 n (1 ≤ n ≤ 105),表示数字里的元素的个数

第二行包含n个整数a1, a2, …, an (1 ≤ ai ≤ 105)

输出

输出一个整数:n你能得到最大分值。

思路

前提: 如果是简单的拿数问题的话:给 n 个数,每一步能拿走一个数,比如拿第 i 个数,Ai = x,得到相应的分数 x,但拿掉这个 Ai 后,Ai+1 和Ai-1 就会变得不可拿。求最大分数。
可以设定状态dp[i]表示在A[1…i]能拿到的最大分数,则dp[i] = max(dp[i-1], dp[i-2]+A[i]) 因为在选择了A[i]之后会影响dp[i-1]的选择,所以可以分成dp[i-1]和dp[i-2]+A[i].

这里变化的是x拿走了,所有等于x+1,x-1的值都不能拿,由位置变成了值,所以可以利用桶排的思想,求出sum[i]即原数组所有等于i的值的总分数,这样,就可以转化成前提做

总结

经过转化,转化成可以利用子问题求整个问题的动态规划问题

反思

由于数据范围是 1 ≤ n ≤ 10^5 ,1 ≤ ai ≤ 10^5,所以总分数dp[i]与sum[i]范围都会超过int,所以用long long

代码

#include<iostream>
#include<cstring>
using namespace std;
long long a[100010],sum[100010];
long long dp[100010];
int main()
{
	int n;cin>>n;
	memset(a,0,sizeof(a));
	memset(sum,0,sizeof(sum));
	memset(dp,0,sizeof(dp));
	for(int i=1;i<=n;i++)
	{
		int num;cin>>num;
		a[num]++;
	}
	for(int i=1;i<=100000;i++)
	sum[i]=a[i]*i;
	dp[0]=0;dp[1]=sum[1];
	for(int i=2;i<=100000;i++)
	{
		dp[i]=max(dp[i-1],dp[i-2]+sum[i]);
	}
	cout<<dp[100000]<<endl;return 0;
	
 } 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值