虽然是半个计算机科班出身,很惭愧地没在大一大二打好C/C++基础,在此前提下,开始看复旦2021年机试上机题目进行学习,若有代码未能准确优化的地方,请各路大佬们一定要积极指出,对我也是莫大的帮助!
复旦2021年第二题爬楼梯
解释:这是一道比较基础的动态规划问题,我一眼就看出了状态转移方程(啊不是,dp都基本记不得了,真是惭愧,我通过找规律的方式发现了其中的奥妙)
当我们尝试走到第k级台阶上的时候,我们有两种方案可以选择:
1、从第k-2级台阶选择爬上两步
2、从第k-1级台阶选择爬上一步
假设到达第k级台阶可能的步数为dp[k],那么很明显有状态转移方程:
这是因为,到达k-1级台阶的可能步数只需再走一步"2级台阶”便能到达k级台阶,而可能步数的数量不变,同理可知k-2级台阶的可能步数数量。
公式有前提:k>=2,所以必须初始化dp[0]=0(在陆地上没有走的方案)、dp[1]=1(走到第一级台阶为一种可能),但是k=2时不符合公式,所以再补上dp[2]=2即可,循环从3开始即可。
代码环节:
#include<iostream>
using namespace std;
const int N=1e5+10;
int dp[N];//可能步数数组
int main()
{
int n;//楼梯数量
cin>>n;
dp[0]=0;
dp[1]=1;
dp[2]=2;//当然为了配合公式可以选择让dp[0]=1,但是逻辑上不是很能说服自己,就选择再多初始化一个
for(int i=3;i<=n;i++) dp[i]=dp[i-1]+dp[i-2]; //状态转移方程
cout<<dp[n];
return 0;
}
2022.2.24更新
今天看到舍友在群里发了一道吃豆子的算法面试题目,看了一眼就发现和这道走楼梯差不多,拿到这里来做个类比,当然没有正确答案,如果有大佬发现错误可以及时向我指出!谢谢!
题目是这样的:
我想的大体思路如下:
首先是要有个动态规划数组dp[k],表示吃k颗豆子的可能情况数量,分以下两种情况:
1、从第k-2颗豆子吃两颗变为k颗
2、从第k-1颗豆子吃一颗变为k颗
第二种情况中dp[k-1]代表了吃k-1颗豆子的情况,每种情况吃一颗就变为k颗,可以直接用。
而第一种情况中dp[k-2]中的所有情况并不能全部算上,因为只有最后一次吃一颗豆子的才能算上,否则将违反连续吃两次2颗豆子的游戏规则。
那么在dp[k-2]中,最后一次吃一颗豆子的可能性为dp[k-3],即为所有吃k-3颗豆子后,最后一颗直接吃的情况。
故有状态转移方程如下:
那么在此前提下,需要提前规定dp[1]=1,dp[2]=2,dp[3]=3。代入方程检验一下发现dp[4]=dp[3]+dp[1]=4,题目中多给了一个条件作为演算。
代码部分特别容易,可以直接写出:
#include<iostream>
using namespace std;
const int N=1010;
int n;//总共有多少颗豆子
int dp[N];//dp数组
int main()
{
cin>>n;
dp[1]=1;//一颗豆子一种吃法
dp[2]=2;//两颗豆子两种吃法
dp[3]=3;//三颗豆子三种吃法
for(int i=4;i<=n;i++)
{
dp[i]=dp[i-1]+dp[i-3];//状态转移方程
}
cout<<dp[n];
return 0;
}
复旦大学2021机试第一题题解:
(不一定正确,简单试了一下demo是对的,望各位大佬发现问题及时指正!)
简单的思路:
感觉第一题还是挺简单的一道题目,我就是用暴力搜索的方法,开一个大型数组,每一次检索所有ancestor即可,一开始把根节点直接加入,以免后面的判断出现问题。
代码部分如下:
#include <iostream>
#include <cstring>
using namespace std;
const int N=1e5+10;
int t[N],flag[N];
int stringtoint(string a)
{
int res=0;
for(int i=0;i<a.size();i++)
{
int p=a[i]-'0';
res=res*10+p;
}
return res;
}
int main()
{
string s;
int i=1;//root number = 1
memset(flag,-1,sizeof flag);//set flag to -1
while(cin>>s)
{
if(s=="null") t[i]=-1;
else t[i]=stringtoint(s);
i++;
if(cin.peek() == '\n') { break; }
}
int res=0; //cout number
flag[1]=1;//root flag is 1,else the loop will be dead
for(int j=1;j<i;j++)
{
if(t[j]!=-1)
{
int m=j/2;
while(m!=0)
{
if(t[m]>t[j]) //if any ancestor is large, break
{
break;
}
m/=2;
}
if(m==0) flag[j]=1;//m have adjusted to 0, all route has been scaned
}
}
for(int k=1;k<i;k++)
{
if(flag[k]!=-1){
res++;
cout<<t[k]<<endl;
}
}
cout<<res;
return 0;
}
复旦大学2021年机试第三题题解:
思考部分如下:
又是一道dp的题目,但是状态转移方程其实很好想,这是一道类似于背包问题的题目,例如思考dp[i][j]为前i个数字通过正负转换加起来得到期待和E=j的个数,那么我们获得这个个数有两个选择,一个是第i个数字取正数,那么前i-1个数字的期待和E=j-a[i],同理,第i个数字取负数,那么前i-1个数字的期待和就应该为E=j+a[i]。状态转移方程就可以得到为:dp[i][j]=dp[i-1][j-a[i]]+dp[i-1][j+a[i]]
但是用这个方法思考,就可能出现背包容量为负数的情况,比如存在第一个数为负数但是没办法下标放到数组中。所以,我们一开始在输入数字的时候可以统计所有数字的和,开辟一个总和*2的数组,将一倍的sum作为原先的期待E=0的情况,所有的判断基于此为中心。当然,一开始我们需要初始化我们的dp数组,将第一行全部初始化,即选第一个数字的情况,当第一个数字为0时,将dp[1][sum]的值设置为2(因为存在+0和-0两种情况),如果第一个数字不是0的话, 那就将dp[1][sum+a[i]]和dp[1][sum-a[i]]的值都设置为1,接下来按照上述状态转移方程计算即可,不明白的可以画一个dp的草图帮助理解。
代码部分如下:
#include <iostream>
#include <cstring>
#include <algorithm>
#include <cmath>
using namespace std;
const int N=1e5+10;
int a[N]; // for number
int main()
{
int i = 1 , sum = 0;
memset(a,-1,sizeof a); //set a to -1
while(cin >> a[i])
{
sum += a[i];
i++;
if(cin.peek() == '\n') { break; }
}
sort(a + 1,a + i - 1);
int e = a[i - 1]; // our expectation
sum -= e; // minus expectation
int dp[i + 10][2 * sum + 10]; //dp array
memset(dp,0,sizeof dp); // dp initialization
for(int k = 0;k <= 2 * sum ;k++)
{
if(k == sum - a[1] || k == sum + a[1])
{if(a[1] != 0) dp[1][k] = 1;
else dp[1][k] = 2;
}
else dp[1][k] = 0;
} //first line will be set
for(int p = 2;p < i - 1 ; p ++)
{
for(int q = 0;q <= 2*sum;q++)
{if(q>=a[p]) dp[p][q] += dp[p - 1][q - a[p]];
dp[p][q] += dp[p - 1][q + a[p]];
cout<<" p:"<<p<<" q:"<<q<<" dp:"<<dp[p][q]<<endl;}
}
cout<<dp[i - 2][e + sum];
return 0;
}
接下去要做2020年的了!