中国矿业大学2021年算法设计与分析实践考试题目以及题解(信安版A卷)

318 阅读4分钟

说在前面:

由于此次考试,不知道是哪位信安的大哥把学校的OJ平台给黑掉了,导致我们100来人同时登陆不上OJ,考试被迫终止半小时.但是由于少部分同学刚开始登录上去了一小会,看到了题目(比如我),所以为了公平起见,学校重新换了一套题. 下面我把两套题以及相应题解放在下面,供大家学习参考!

A卷:

1.凯撒加密法

题目描述: 凯撒加密法,或称恺撒加密、恺撒变换、变换加密,是一种最简单且最广为人知的加密技术。它是一种替换加密的技术,明文中的所有字母都在字母表上向后(或向前)按照一个固定数目进行偏移后被替换成密文。 例如,当偏移量是左移3的时候: 明文字母表:ABCDEFGHIJKLMNOPQRSTUVWXYZ 密文字母表:DEFGHIJKLMNOPQRSTUVWXYZABC 使用时,加密者查找明文字母表中需要加密的消息中的每一个字母所在位置,并且写下密文字母表中对应的字母。需要解密的人则根据事先已知的密钥反过来操作,得到原来的明文。例如: 明文:THE QUICK BROWN FOX JUMPS OVER THE LAZY DOG 密文:WKH TXLFN EURZQ IRA MXPSV RYHU WKH ODCB GRJ 现在给定你一个字符串S(长度不会超过1000000)和一个整数k(-1000000000<=k<=1000000000),分别代表接受者收到的密文和在加密该密文时向后的偏移量,你的任务是计算出原来的明文 注意:只有字母在加密时才会发生偏移,其它字符保持不变.

输入: 输入包含多组数据,其中第一行为数据组数T(T<=10) 每组数据第一行为一个字符串S,由数字、字母以及常见字符组成(不含空格),第二行为一个整数k代表加密时向后的偏移量(|S|<=1000000,-1000000000<=k<=1000000000)

输出: 对每组数据,输出一行字符串,代表输入中的密文对应的明文。

样例输入: 样例输入:

 1
 DEFGHIJKLMNOPQRSTUVWXYZABC
 3

样例输出:

 ABCDEFGHIJKLMNOPQRSTUVWXYZ

题解: 水题,只需要简单变换一下字符的位置即可.

 #include<bits/stdc++.h>
 using namespace std;
 int main()
 {
     int t;
     cin>>t;
     while(t--)
     {
         string str;
         cin>>str;
         int k;
         cin>>k;
         int n = str.size();
         for(int i=0; i<n; i++)
         {
             str[i] = 'A' + (str[i]-'A'-k+26)%26;
         }
         cout<<str<<endl;
     }
     return 0;
 }

2.跳跃游戏Ⅱ

题目描述: 给定一个非负整数数组,假定你的初始位置为数组第一个下标。数组中的每个元素代表你在那个位置能够跳跃的最大长度。你的目标是到达最后一个下标,并且使用最少的跳跃次数。例如:A = [2,3,1,1,4],到达最后一个下标的最少跳跃次数为 2。(先跳跃11步,从下标0到1,然后跳跃3步,到达最后一个下标。一共两次)

输入: 第一行输入一个正整数n(1≤n≤100),接下来的一行,输入n个整数,表示数组A。

输出: 最后输出最少的跳跃次数。

样例输入:

 5
 3 1 1 1 1

样例输出:

 2

题解: 这道题和之前在力扣上做过的题一样的,比较简单. 如果某一个作为 起跳点 的格子可以跳跃的距离是 3,那么表示后面 3 个格子都可以作为 起跳点.可以对每一个能作为 起跳点 的格子都尝试跳一次,把 能跳到最远的距离不断更新。 如果从这个 起跳点 起跳叫做第 1 次 跳跃,那么从后面 3 个格子起跳 都 可以叫做第 2 次 跳跃。 所以,当一次 跳跃 结束时,从下一个格子开始,到现在能跳到最远的距离,都 是下一次 跳跃 的 起跳点。 对每一次 跳跃 用 for 循环来模拟。跳完一次之后,更新下一次 起跳点 的范围。在新的范围内跳,更新 能跳到最远的距离.记录 跳跃 次数,如果跳到了终点,就得到了结果。

 #include<bits/stdc++.h>
 using namespace std;
 int Jump(vector<int> &nums)
 {
     int n = nums.size(); 
     int start = 0;  //每一轮起跳的起跳点
     int end = 0;  //每一轮跳跃能够到达的最远
     int Max_pos = 0;   
     int ans = 0;  //记录跳跃次数
     while(Max_pos < n-1)
     {
         for(int i=start; i<=end; i++)  //不断更新能够跳跃的最远距离
         {
             Max_pos = max(Max_pos, i+nums[i]);
         }
         start = end+1;  //上一轮跳跃到达终点之后,重新赋值给起跳点
         end = Max_pos;  //更新此次跳跃最远能够到达的距离
         ans++;
     }
     return ans;
 }
 int main()
 {
     int n;
     cin>>n;
     vector<int> nums(n);
     for(int i=0; i<n; i++) cin>>nums[i];
     cout<<Jump(nums)<<endl;
     return 0;
 }

3.LongestMatch

题目描述: 一家新成立的侦探事务所正在利用有限的情报搜集侦探之间的秘密情报传递技术。由于他们是这个行业的新人,他们很清楚自己的信息很容易被其他群体捕获并修改。他们想通过检查消息的更改部分来猜测其他组的意图。首先,他们必须得到最长匹配的长度。你要帮助他们。

输入: 输入文件可能包含多个测试用例。每个case将包含两个连续的字符串行。可能出现空白行和非字母打印标点字符。每一行字符串将不超过1000个字符。每个单词的长度不能超过20个字符。

输出: 对于每一种输入,您都必须输出一行,以字段宽度为2的无右对齐大小写开头,然后是最长的匹配,如示例输出所示。如果每个输入至少有一个空行,则输出' blank !'。将非字母标点字符视为空白。

样例输入:

 This is a test.
 test
 Hello!
 ​
 The document provides late-breaking information
 late breaking.

样例输出:

 1. Length of longest match: 1
 2. Blank!
 3. Length of longest match: 2

题解: 我最初的想法很简单,那就是先把两个字符串分割储存到两个容器中,然后对于其中一个容器,依次取出其中一个元素,查找是否存在于另一个容器中.最后得到答案.

 #include<bits/stdc++.h>
 using namespace std;
 vector<string> split(string str)  //将一个字符串从空格处分开,非字符也算作空格
 {
     vector<string> vec;
     int len = str.length();
     bool flag = true; //表示一个新单词开始与否
     int start = 0; //每个单词开始的下标
     for(int i=0; i<len; ++i)
     {
         if(flag && isalpha(str[i]))
         {
             flag = false;
             start = i;
         }
         else if(!isalpha(str[i]))
         {
             flag = true;
             vec.push_back(str.substr(start, i-start));
         }
     }
     if(flag == false) vec.push_back(str.substr(start, len-start)); //最后一个为字母的话
     return vec;
 }
 int LongestMatch(vector<string> vec1, vector<string> vec2)
 {
     int ans = 0;
     int size = vec2.size();
     for(int i=0; i<size; i++)
     {
         if(find(vec1.begin(), vec1.end(), vec2[i]) != vec1.end()) ans++;
     }
     return ans;
 }
 int main()
 {
     string str1, str2;
     int i=1;
     while(getline(cin, str1) && getline(cin, str2))
     {
         if(str1.empty() || str2=="")
         {
             printf("%2d.", i++);
             cout<<"Blank!"<<endl;
             continue;
         }
         vector<string> vec1, vec2;
         vec1 = split(str1);
         vec2 = split(str2);
         printf("%2.d", i++);
         cout<<"Length of longest match: "<<LongestMatch(vec1, vec2)<<endl;
     }
     return 0;
 }

4.Schedule

题目描述: 有N个时间表,第i个时间表有开始时间si和结束时间ei (1 <= i <= N)。每两个重叠的时间表不能在同一台机器上执行。对于每台机器,工作时间定义为timeend和timestart之间的差值,其中time_{end}是关闭机器的时间,timestart是打开机器的时间。我们假设机器不能在时间开始和时间结束之间关闭。打印执行所有计划的最小机器数K,当只使用K台机器时,打印所有工作时间的最小总和。

输入: 第一行包含一个整数T (1 <= T <= 100),表示测试用例的数量。每一种情况都以包含一个整数N (0<N<=100000)。接下来的N行包含两个整数si和ei (0<=si<ei<=1e9)。

输出: 对于每个测试用例,打印最小可能的机器数量和所有工作时间的最小总和

样例输入:

 1
 3
 1 3
 4 6
 2 5

样例输出:

 2 8

题解: 贪心算法,思路: 我们要知道,本题不仅要求所需要的机器数,还要求总时间.总时间由每个任务的时间加上机器等待的时间. 我们先进行一个特殊的排序,首先将每个任务的开始时间和结束时间都拆分掉,如果一共有n个任务,那么我们将得到2*n个节点.每个节点有两个信息,一个是时间Time,一个是标志Flag(用来表明该节点是开始时间还是结束时间).然后进行从小到大的排序,我们还要注意一点的是,如果有两个节点的时间相同,那么是结束时间的那个放前面.这很显然,因为

每次新添加一个任务时,都选择现在已有机器中结束时间最晚的(用一个栈来实现),这样我们就会得到最短的等待时间.

 // Schedule
 // 贪心算法
 // 题目大意:有 N个时间表,他们分别有自己的开始时间和结束时间,
 // 同时,也有很多机器,时间表上的时间是使用机器的时间,然而,
 // 一台机器不能在重复的时间被使用,所以,要完成时间表的任务,可能需要多台机器,
 // 题目要求的就是符合要求的最少机器数和相应时间之和(机器等待的时间也算)。
 #include<bits/stdc++.h>
 using namespace std;
 struct Node
 {
     int T;     
     int flag;      //1表示该节点是开始时间,2表示是结束时间
 }nodes[3000];
 bool cmp(Node &a, Node &b)
 {
     if(a.T != b.T) return a.T<b.T;
     else return a.flag > b.flag; //如果两个时间相同,是结束时间的那个放前面.很显然,在这之前肯定有一个与它对应的开始时间.这个任务已经完成.
 }
 int main()
 {
     int x;
     scanf("%d", &x);
     while(x--)
     {
         int count;
         int pos = 0;  //表示节点数组的位置
         int machine = 0;
         int ans_mach = 0;
         long long ans_sum = 0;
         stack<int> endtime;     //结束时刻存放在栈中
         scanf("%d", &count);
         while(!endtime.empty()) endtime.pop();  //保证endtime数组清空
         for(int i=0; i<count; i++)
         {
             scanf("%d", &nodes[pos].T);
             scanf("%d", &nodes[pos+1].T);
             nodes[pos].flag = 1;
             nodes[pos+1].flag = 2;
             ans_sum = ans_sum + (nodes[pos+1].T - nodes[pos].T);  //先把所有工作任务的时间累加
             pos += 2;
         }
         sort(nodes, nodes+pos, cmp);        //从小到大排序
         for(int i=0; i<pos; i++)
         {
             if(nodes[i].flag == 1)
             {
                 machine++;
                 if(!endtime.empty())
                 {
                     int t = endtime.top();
                     endtime.pop();
                     ans_sum += (nodes[i].T - t);  //我们并不关心位于栈顶这个结束时间所对应的开始时间是哪个,因为计算等待时间只需要知道结束时间即可.而且根据大小排序之后,可以保证,新加入的这个开始时间一定可以在任意一台已经有的机器上工作!(因为目前所有的结束时间都是小于该开始时间的,所以这也是一个我们不需要关心(开始_结束)对应的一个原因.)
                 }
             }
             else
             {
                 machine--;
                 endtime.push(nodes[i].T);
             }
             ans_mach = max(ans_mach, machine);  //更新最大机器数
         }
         printf("%d %lld\n", ans_mach, ans_sum);
     }
     return 0;
 }   

5.Wavio Sequence

题目描述: 题目大意是给出一个数组,找出该数组中存在的最大Wavio Sequence序列长度. Wavio Sequence是什么?很简单,你看看下面两个例子就明白了! Wavio是一个整数序列,具有以下特性:

  1. Wavio序列的长度是奇数, 即 L = 2 * n + 1
  2. Wavio序列前 n+1 个整数是递增序列
  3. Wavio序列后 n+1 个整数是递减序列

如示例 1 2 3 4 5 4 3 2 1 10 最长的 Wavio序列 为 1 2 3 4 5 4 3 2 1 ,所以答案为9

输入: 输入文件包含少于75个测试用例。每个测试用例的描述如下: 每个集合从一个正整数N(1<=N<=10000)开始。在接下来的几行中有N个整数。

输出: 对于每一组输入,在一行中打印最长的wavio序列的长度。

样例输入:

 10
 1 2 3 4 5 4 3 2 1 10
 19
 1 2 3 2 1 2 3 4 3 2 1 5 4 1 2 3 2 2 1
 5
 1 2 3 4 5

样例输出:

 9
 9
 1

题解: 1.朴素暴力 一看这题,不就是求最长上升子序列吗?正着来一遍,倒着来一遍就可以了. 然后看看哪个位置上二者之和最大,那么就是答案. 时间复杂度:0(n^2)

代码如下:

 #include<bits/stdc++.h>
 using namespace std;
 const int inf = 10000;
 // 朴素算法,0(n^2)
 vector<int> Compute(vector<int> &nums)
 {
     int n = nums.size();
     vector<int> dp(n, 1);
     for(int i=0; i<n; i++)
     {
         for(int j=0; j<i; j++)
         {
             if(nums[i]>nums[j])
             {
                 dp[i] = max(dp[j]+1, dp[i]);
             }
         }
     }
     for(int &x:dp)    //基于范围的for循环中可通过引用修改数组元素中的值,直接修改只是修改副本,原始数组数据不变。
     {
         if(x == 1)  x = -inf;
     }
     return dp;
 }
 int main()
 {
     int n;
     while(cin>>n && n)
     {
         vector<int> nums(n);
         for(int i=0; i<n; i++) cin>>nums[i];
         vector<int> vec1 = Compute(nums);
         reverse(nums.begin(), nums.end());
         vector<int> vec2 = Compute(nums);
         int max_ans = 1;
         for(int i=0; i<n; i++)
         {
             max_ans = max(max_ans, vec1[i]+vec2[n-i-1]-1);
         }
         cout<<max_ans<<endl;
     }
     return 0;
 }

但是可能的话,会卡时间.TLE

2.贪心算法 + 二分查找 所以想到另一种优化的方法,通过二分查找和贪心算法来实现最长上升子序列长度的计算. 这与力扣主站第300题 --- 最长递增子序列相同.我们主要关注题解的第二种方法,传送门在这里,看完之后就很容易理解这一道题目了! 时间复杂度:O(nlogn)

代码如下:

 // 贪心算法 + 二分查找 求最长上升子序列的长度  O(nlogn) 
 // 考虑一个简单的贪心,如果我们要使上升子序列尽可能的长,则我们需要让序列上升得尽可能慢,因此我们希望每次在上升子序列最后加上的那个数尽可能的小。
 // 维护一个一维数组B,并且这个数组是动态扩展的,初始大小为1,B[i]表示最长上升子序列长度是i的所有子串中末尾最小的那个数,
 // 我们依次遍历数组 nums 中的每个元素,并更新数组 B 和 len 的值。如果nums[j] > B[len] 则更新 len = len + 1
 // 否则在 B[1...len] 中 找到 B[i-1] < nums[j] <  B[i]的pos值,然后更新B[i] = nums[j]
 #include<bits/stdc++.h>
 using namespace std;
 const int inf = 10000;
 int lengthOfLIS(vector<int> &nums, int end)    //得到数组nums[1..end]的最大上升子序列长度
 {
     int n = nums.size();
     if(n == 0) return 0;
     vector<int> B(n+1, 0);
     B[1] = nums[0];
     int len = 1;  //记录最长上升子序列的长度
     for(int i=1; i<end; ++i)
     {
         if(nums[i] > B[len]) B[++len] = nums[i];
         else
         {
             int l = 1, r = len, pos = 0;
             while(l <= r)
             {
                 int mid = (l+r) >> 1;
                 if(B[mid] < nums[i])
                 {
                     pos = mid;
                     l = mid+1;
                 }
                 else r = mid-1;
             }
             B[pos+1] = nums[i];
         }
     }
     return len;
 }
 int Ans(vector<int> &nums)
 {
     int n = nums.size();
     vector<int> LIS_length;
     vector<int> LIS_length_Reverse;
     for(int i=1; i<=n; i++)
         LIS_length.push_back(lengthOfLIS(nums, i));  //i为end,所以i<=n
     reverse(nums.begin(), nums.end());  //反转数组
     for(int i=1; i<=n; i++)
         LIS_length_Reverse.push_back(lengthOfLIS(nums, i));
     for(int i=0; i<n; ++i)
     {
         if(LIS_length[i]==1) LIS_length[i] = -inf;
         if(LIS_length_Reverse[i]==1) LIS_length_Reverse[i] = -inf;
     }
     int max_ans = 1;
     for(int i=0; i<n; ++i)
         max_ans = max(max_ans, LIS_length[i]+LIS_length_Reverse[n-i-1]-1);
     return max_ans;
 }
 int main()
 {
      int n;
     while(cin>>n && n)
     {
         vector<int> nums(n);
         for(int i=0; i<n; i++) cin>>nums[i];
         cout<<Ans(nums)<<endl;
     }
     return 0;
 }

以上就是全部五道题,其实说实话,我觉得有点难度.幸好有大哥黑了OJ平台😀,所以换了一套简单的题目. 如果你觉得本篇文章对你有所帮助,欢迎大家去我的个人博客 --- 乔治的编程小屋逛逛.