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
思路
-
一开始我想用广度优先搜索来做这道题,但觉得这样做太麻烦了,这道题更像是一道结论题。
-
果然让我找到结论了,我们不断做(m/n) /(2或3)除法就可以获得结果,步骤如下:
- 如果m不能整除n,说明m根本不是n乘出来的,报错;反之m=m/n。
- 接下来进入循环,每轮循环让m=m/(2或3);2或3都不能整除的话,也说明m不是2或3乘出来的,报错。。
- 最后循环次数就是我们的结果。
心得
- 这套题差点做成一道图论题,还好多想了一会儿,最终把它做成了结论题。(这应该是结论题吧)
代码
#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
思路
动态规划几大步:
- 确定dp[i]的意义
- 确定初始状态
- 确定状态方程
- 确定最终结果
LIS最长上升子序列 LCS最长公共子序列
这两个子序列是字符串动态规划的典型题目,套路如下:
LIS最长上升子序列
顾名思义就是输入序列里可以分割出烧过严格升序的子序列,选取这些子序列里面最长的一条,输出长度。
注意了,这里没说子序列必须连续,因此断断续续的递增也可以。
- dp[i]代表从a[0]头到a[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); } }
LCS最长公共子序列
需要注意,这里同样可以不连续
这道题有两个字符串,两个字符串都可以有自己的下标,因此采取二二维数组。
- 确定dp意义:
dp[N][M]
代表截止到a串第N个和b串第M个元素,公共子串有多长 - 确定初始值:全部为0
- 确定状态方程:
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]);//否则取临近两个状态中的最大长度
- 确定最终结果:
dp[N][M]
就是结果
心得
经过这两个月的面试磨炼,我在基础知识和项目讲解方面都锻炼得差不多了,但是算法方面非常不足。总结原因有两个,第一是平时练习不足,第二是做题的时候没办法用尽所有库存,细节老出错。
对于第一个原因,我正在着力合理规划时间,去掉娱乐和发呆,时间应该够用,时间管理这方面应该向娱乐圈某时间管理大师看齐(雾)。
针对第二个原因,主要原因是我平时总是在补作业+听课双线工作,导致无法集中所有脑力来编码,这一点没得搞,毕竟双倍课程…我要努力那第一条挤出来的水分补第二条的坑。
报告里面题目简介的数据要求那块可以单独搞一个大标题,这样可以锻炼对数据的注意力
-
dp数组开全局,所有东西都开全局,时刻担心栈空间太难受。
-
错以为只有一个n,导致debug半小时
-
数组初始化
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])
就可以实现不拿两种相邻数的效果。
- dp只包含总0到i的结果,+1被忽略。
func(dp[i-2])
,-1被跳过。
接下来,确定dp代表什么意义,因为结果要求取得最大和,那我们dp也取最大和。
还要注意,分数的
最后,套路如下:
- 统计每种数出现的次数times
- 因为不确定第一个数min_score最终是否能被取出来,因此初态dp[min_times-1]=0
- 从i=min_times开始向右遍历,
dp[i] = max(dp[i - 1], dp[i - 2] + i * times[i]);
。这里i相当于分数。 - 最终结果是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;
}
总结
近来又受到细节问题的困扰,总结做题时有两条线要同时走:
- 解题思路
- 细节,比如是否与库函数重名,是否有类型转换错误,是否有空指针,标识符书写是否匹配等等,可以说在编写代码中的每个字符时脑子里都要有他们的注意事项。