算法-程序设计课week10-作业

A-签到题

题目

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

Input

输入的唯一一行包括两个整数n和m(1<=n<=m<=5*10^8).

Output

输出从n转换到m的操作次数,否则输出-1.

Simple Input 1

120 51840

Simple Output 1

7

Simple Input 2

42 42

Simple Output 2

0

Simple Input 3

48 72

Simple Output 3

-1

思路

  1. 一开始我想用广度优先搜索来做这道题,但觉得这样做太麻烦了,这道题更像是一道结论题。

  2. 果然让我找到结论了,我们不断做(m/n) /(2或3)除法就可以获得结果,步骤如下:

    1. 如果m不能整除n,说明m根本不是n乘出来的,报错;反之m=m/n。
    2. 接下来进入循环,每轮循环让m=m/(2或3);2或3都不能整除的话,也说明m不是2或3乘出来的,报错。。
    3. 最后循环次数就是我们的结果。

心得

  1. 这套题差点做成一道图论题,还好多想了一会儿,最终把它做成了结论题。(这应该是结论题吧)

代码

#include <bits/stdc++.h>
using namespace std;
#define LL long long
#define dprintf printf
int main() {
	LL n, m;
	LL res = 0;
	scanf("%lld%lld", &n, &m);

	if (m % n != 0) {  //如果m无法整除n,报错
		printf("-1\n");
		return 0;
	} else {  //若能整除,则继续
		m = m / n;
		while (m != 1) {	   //直到m除到n
			if (m % 2 == 0) {  //整除2
				m = m / 2;
			} else if (m % 3 == 0) {  //整除3
				m = m / 3;
			} else {  //无法整除,报错
				printf("-1\n");
				return 0;
			}
			// dprintf(":%d\n",m);
			res++;
		}
		printf("%d\n", res);
	}
	return 0;
}

B - LIS & LCS

题目

东东有两个序列A和B。
他想要知道序列A的LIS和序列AB的LCS的长度。
注意,LIS为严格递增的,即a1<a2<…<ak(ai<=1,000,000,000)。

Input

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

Output

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

Simple Input

5 5
1 3 2 5 4
2 4 3 1 5

Simple Output

3 2

思路

动态规划几大步:

  1. 确定dp[i]的意义
  2. 确定初始状态
  3. 确定状态方程
  4. 确定最终结果

LIS最长上升子序列 LCS最长公共子序列

这两个子序列是字符串动态规划的典型题目,套路如下:

LIS最长上升子序列

顾名思义就是输入序列里可以分割出烧过严格升序的子序列,选取这些子序列里面最长的一条,输出长度。

注意了,这里没说子序列必须连续,因此断断续续的递增也可以。

  1. dp[i]代表从a[0]头到a[i]这段子序列中的最长上升子序列长度是多少。
  2. 状态方程
    	dp[i] = 1;
    	for (LL j = i - 1; j > 0; j--) {
    		if (a[j] < a[i]) {
    			dp[i] = max(dp[i], dp[j] + 1);
    		}
    	}
    

LCS最长公共子序列

需要注意,这里同样可以不连续

这道题有两个字符串,两个字符串都可以有自己的下标,因此采取二二维数组。

  1. 确定dp意义:dp[N][M]代表截止到a串第N个和b串第M个元素,公共子串有多长
  2. 确定初始值:全部为0
  3. 确定状态方程:
			if (a[i] == b[j])
				dp2[i][j] = dp2[i - 1][j - 1] + 1;//若当前两串元素相等,则公共子串是上一个索引的长度加一
			else
				dp2[i][j] = max(dp2[i - 1][j], dp2[i][j - 1]);//否则取临近两个状态中的最大长度
  1. 确定最终结果:dp[N][M]就是结果

心得

经过这两个月的面试磨炼,我在基础知识和项目讲解方面都锻炼得差不多了,但是算法方面非常不足。总结原因有两个,第一是平时练习不足,第二是做题的时候没办法用尽所有库存,细节老出错。

对于第一个原因,我正在着力合理规划时间,去掉娱乐和发呆,时间应该够用,时间管理这方面应该向娱乐圈某时间管理大师看齐(雾)。
针对第二个原因,主要原因是我平时总是在补作业+听课双线工作,导致无法集中所有脑力来编码,这一点没得搞,毕竟双倍课程…我要努力那第一条挤出来的水分补第二条的坑。

报告里面题目简介的数据要求那块可以单独搞一个大标题,这样可以锻炼对数据的注意力

  1. dp数组开全局,所有东西都开全局,时刻担心栈空间太难受。

  2. 错以为只有一个n,导致debug半小时

  3. 数组初始化

    LL dp[n1][n2];//这样只会初始化一维数组
    

代码

#include <bits/stdc++.h>
using namespace std;
#define LL long long
LL a[5010];
LL b[5010];

LL dp[5010];
LL LISDP(LL* a, LL n) {
	LL maxi = 1;
	dp[1] = 1;
	for (LL i = 2; i <= n; i++) {
		dp[i] = 1;
		for (LL j = i - 1; j > 0; j--) {
			if (a[j] < a[i]) {
				dp[i] = max(dp[i], dp[j] + 1);
			}
		}
		if (dp[i] > dp[maxi]) {
			maxi = i;
		}
	}
	return dp[maxi];
}

LL dp2[5010][5010];
LL LCSDP(LL* a, LL* b, LL n, LL m) {
	memset(dp2, 0, sizeof(dp2));
	for (LL i = 1; i <= n; i++) {
		for (LL j = 1; j <= m; j++) {
			if (a[i] == b[j])
				dp2[i][j] = dp2[i - 1][j - 1] + 1;
			else
				dp2[i][j] = max(dp2[i - 1][j], dp2[i][j - 1]);
		}
	}
	return dp2[n][m];
}

int main() {
	LL n, m;
	scanf("%lld %lld", &n, &m);
	for (LL i = 1; i <= n; i++)
		scanf("%lld", &a[i]);
	for (LL i = 1; i <= m; i++)
		scanf("%lld", &b[i]);

	printf("%lld %lld\n", LISDP(a, n), LCSDP(a, b, n, m));
	return 0;
}

未曾设想的问题

求问下面这两段代码中,为何第一个AC第二个WA。

//AC!!!!
LL LISDP(LL* a, LL n) {
	LL maxi = 1;
	dp[1] = 1;
	for (LL i = 2; i <= n; i++) {
		dp[i] = 1;
		for (LL j = i - 1; j > 0; j--) {
			if (a[j] < a[i]) {
				dp[i] = max(dp[i], dp[j] + 1);
			}
		}
		if (dp[i] > dp[maxi]) {
			maxi = i;
		}
	}
	return dp[maxi];
}
//WA!!!
LL dp[5010];
LL LISDP(LL* a, LL n) {
	LL maxv=-1;
	dp[1] = 1;
	for (LL i = 2; i <= n; i++) {
		dp[i] = 1;
		for (LL j = i - 1; j > 0; j--) {
			if (a[j] < a[i]) {
				dp[i] = max(dp[i], dp[j] + 1);
			}
		}
		if (dp[i] > maxv) {
			maxv = dp[i] ;
		}
	}
	return maxv;
}

C - 拿数问题 II

YJQ 上完第10周的程序设计思维与实践后,想到一个绝妙的主意,他对拿数问题做了一点小修改,使得这道题变成了 拿数问题 II。
给一个序列,里边有 n 个数,每一步能拿走一个数,比如拿第 i 个数, Ai = x,得到相应的分数 x,但拿掉这个 Ai 后,x+1 和 x-1 (如果有 Aj = x+1 或 Aj = x-1 存在) 就会变得不可拿(但是有 Aj = x 的话可以继续拿这个 x)。求最大分数。
本题和课上讲的有些许不一样,但是核心是一样,需要你自己思考。

Input

第一行包含一个整数 n (1 ≤ n ≤ 105),表示数字里的元素的个数
第二行包含n个整数a1, a2, …, an (1 ≤ ai ≤ 105)

Output

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

Example
Input

2
1 2

Output

2

Input

3
1 2 3

Output

4

Input

9
1 2 1 3 2 2 2 2 3

Output

10

Hint

对于第三个样例:先选任何一个值为2的元素,最后数组内剩下4个2。然后4次选择2,最终得到10分。

思路

本题关键在于拿了一个数之后+1-1的两个相邻数就都不能拿了,如何把它应用到dp上?

可以想出来,dp[i]=func(dp[i-2])就可以实现不拿两种相邻数的效果。

  1. dp只包含总0到i的结果,+1被忽略。
  2. func(dp[i-2]),-1被跳过。

接下来,确定dp代表什么意义,因为结果要求取得最大和,那我们dp也取最大和。

还要注意,分数的

最后,套路如下:

  1. 统计每种数出现的次数times
  2. 因为不确定第一个数min_score最终是否能被取出来,因此初态dp[min_times-1]=0
  3. 从i=min_times开始向右遍历,dp[i] = max(dp[i - 1], dp[i - 2] + i * times[i]);。这里i相当于分数。
  4. 最终结果是dp[max_score]

心得

这道题目我觉得比较巧,想出来就觉得比较简单,想不出来就彻底想不出来了。
一开始输入的n不代表dp次数,输入的数值大小决定dp次数,这是比较巧妙的地方。

代码


#include <bits/stdc++.h>
using namespace std;
#define LL long long

LL n;
LL times[100000 + 10] = {0};
LL dp[100000 + 10] = {0};

int main() {
	scanf("%lld", &n);

	//统计输入数字出现次数
	//同时取得最大最小数字,减少dp次数,这里可以不统计
	LL max_score = -1;
	LL min_score = 100000 + 10;
	for (LL i = 1; i <= n; i++) {
		LL temp_score;
		scanf("%lld", &temp_score);
		max_score = max(max_score, temp_score);
		min_score = min(min_score, temp_score);
		times[temp_score]++;
	}

	//进行dp
	for (LL i = min_score; i <= max_score; ++i) {
		dp[i] = max(dp[i - 1], dp[i - 2] + i * times[i]);
	}

	//最终结果
	printf("%lld\n", dp[max_score]);
	return 0;
}

总结

近来又受到细节问题的困扰,总结做题时有两条线要同时走:

  1. 解题思路
  2. 细节,比如是否与库函数重名,是否有类型转换错误,是否有空指针,标识符书写是否匹配等等,可以说在编写代码中的每个字符时脑子里都要有他们的注意事项。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值