湖南科技大学2023年大学生计算机程序设计新生赛题解

目录

前言

A.2023 is the best

难度:0颗星

B.小小怪的珠宝

题意:

解题思路:

难度:1颗星

 C.小Z的零比特填充

题意:

解题思路:

难度:1颗星

D. 具有嘲讽技能的随从

题意:

解题思路:

难度:1颗星

E. 小A的魔法符文Ⅰ

题意:

 解题思路:

难度:2颗星

F. 小A的魔法符文Ⅱ

题意:

解题思路:

难度:3颗星

G. jeazim的正方形

题意:

解题思路:

难度:3颗星

H. S级技能

题意:

解题思路:

难度: 3颗星

 I. 小A想要斗地主

题意:

解题思路:

难度:2.5颗星

 J. 金铲铲

题意:

难度:5颗星

解题思路:

算法一:

算法二:

算法三:

算法四:

算法五:

K. 花花花

结语


前言

首先请容许我作为一个要退役的老队员说说自己的一些感慨。

两年前我也是坐在那个比赛的场地,面对着屏幕上的题目一道一道的琢磨,有时会因为卡题而感到焦急,有时会因为突然迸发的思路而感到欣喜,还有每次看到榜单上的排名时都会激动不止,再到最后拿到奖时兴奋不已。

现在的我已经是出题人的身份,也作为志愿者几乎看完了比赛的全程,我是以旁观者过来人的视角看着大家坐在赛场,就像我当年那样坐在那里奋战,曾经那丰富的情绪多次涌上心头,那份真切的回忆是永远忘却不了的。

可以十分真诚的说,我看着大家写题,特别是写我自己出的题,这种成就感就足让我感到十分的满足十分的幸福,虽然我为大家出题和解题所投入的大量时间并不能给我物质上的回报,但是我也乐在其中,特别是能帮助到一些人我也会感到很开心(^_^)。

话不多说,开始讲题——

这次的题目整体难度比起往年是要偏容易的,目的就是可以更好的通过过题数清晰的将比赛结果分层。不过整体容易,最后两题还是很有难度的。

我的题解也和以前一样,为了能够比较清晰的让大家了解题目难度的分布,将题目给予一定的star数,star数量越多越难

我根据star数大致说明今年试题的难度分布,首先题目大体上是按照难度排序的,具体上也不完全按照难度排序(比如BCD题难度并非B<C<D),这个也是因人而异。

大体情况如下:其中A题0颗星属于签到题,BCD都为1颗星属于水题需要一定的基础思维,从E开始的2星题就开始升级难度了,到FGHI的3星就是中档题需要思维技巧加一定的编程能力,I题是模拟题主要考察编程能力,JK题是压轴题难度远高于前面几题。

关于C++代码新手不熟悉的问题,我这里的题解代码虽然表面是C++,但实质上只用到了C++的输入和输出,其他的都是C语言的知识,所以没学过C++的同学也是可以看的

附(C++输入输出流与C标准输入的等价变换):

//C++输入流与C语言标准输入,下面两个方法等价
int a;
cin>>a;
scanf("%d",&a);


//C++输出流与C语言标准输出,下面两个方法等价
cout<<a;
printf("%d",a);

由于时间关系,我先写了一部分题的题解,后续会慢慢更新——

A.2023 is the best

和往常一样,A题作为一个签到题,只要会C语言的基本语法做出来都没什么问题,就不多解释。

难度:0颗星

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

int main()
{
    int n;
    cin >> n;
    if (n == 2023)
        cout << "2023 is the best.";
    else
        cout << n << " is not the best.";
    return 0;
}

B.小小怪的珠宝

题意:

输入给出不同的时间珠宝的n个不同的价格,选择一个最佳的买入卖出时间使得盈利最大化。注意买入时间需要在卖出时间之前

解题思路:

我们可以循环遍历一次数组,每次求当前遍历到的售价与前面日期中售价的最小值之间的差值,输出差值最大的即可。

难度:1颗星

#include <bits/stdc++.h>
using namespace std;
int n, a[1000005], mn = 1e9 + 5, ans; // mn记录当前最小值,ans记录答案

int main()
{
    cin >> n;
    for (int i = 0; i < n; i++) {
        cin >> a[i];
        if (mn > a[i])   
            mn = a[i];      // 更新当前最小值
        ans = max(a[i] - mn, ans);   // 更新最大盈利
    }
    if (ans)
        cout << ans;
    else
        cout << "Little monster lost";
    return 0;
}

 C.小Z的零比特填充

题意:

输入一个字符串,对字符串执行两种操作:
1. 对每连续的五个1 在其后添加一个0;

2. 对每连续的五个1 在其后删除一个0。

解题思路:

考察模拟,用代码模拟一下过程即可,可以和我一样用一个计数器cnt记录连续的1的数量,当cnt为5时执行相应的操作。具体细节可以结合代码理解。

难度:1颗星

#include <bits/stdc++.h>
using namespace std;
char op;
char str[200005], ans[200005];  // str为原字符串,ans为目的字符串

int main()
{
    cin >> op >> str;
    int cnt = 0;  //记录连续的1的数量
    for (int i = 0, j = 0; i <= strlen(str); i++)
    {
        ans[j++] = str[i];
        if (str[i] == '1')
        {
            cnt++;
            if (cnt == 5)
            {
                if (op == 'S')
                    ans[j++] = '0';
                else
                    i++;
                cnt=0;
            }
        }
        else
            cnt = 0;
    }
    cout << ans;
    return 0;
}

D. 具有嘲讽技能的随从

这是我命的一道题目,也是作为一个签到题出来的,但是我设计了一个坑,来考察大家会不会注意到一些细节。看到大家的提交确实有很多人刚开始都wa掉了,这也是符合我的预期的。

题意:

简单来说就是你有n个具有一定攻击力的随从,每个随从具有一次进攻机会,问你能否有一种进攻顺序在解决掉对面具有m滴血量的随从之后仍然有具有攻击力的随从未使用。注意未使用的必须是具有攻击力的随从,因为题目中要求的是要扣除敌方英雄血量,如果剩余的随从攻击力为0,那也就不能扣除血量!这就是这题的坑点。

解题思路:

题目的考察点是贪心,我们需要尽量优先用攻击力大的随从来攻击敌方随从,这样我们才最有可能在击杀敌方随从后仍然有具有攻击力的随从未使用。那我们可以求出所有随从的攻击力之和,然后减去最小的非零攻击力,比较这个值和血量m之间的大小即可。所以题目也就转变成了求数组之和,与找出数组中的最小非零值,很简单吧

难度:1颗星

#include <bits/stdc++.h>
using namespace std;
int n, m, mn = 1e9 + 5, sum; // sum记录攻击力总和,mn记录最小非零攻击力
int a[100005];

int main()
{
    cin >> n >> m;

    for (int i = 0; i < n; i++)
    {
        cin >> a[i];
        sum += a[i];
        if (a[i] && a[i] < mn)
            mn = a[i];
    }
    if (sum - mn >= m)
        cout << "yes";
    else
        cout << "no";
    return 0;
}

E. 小A的魔法符文Ⅰ

挺有意思的一个题目,考察点是贪心,很巧妙。如果发现了突破点,这个题也就相当简单,但是如果没发现,那这个题估计会被卡住。

题意:

输入给出n个符文,每次操作可以选择其中的x,y符文进行融合生成x,x+y两个符文。注意这里融合是生成x和x+y,而不仅仅是x+y(本人之前就看错了题意导致结果不对)。问最少需要多少次操作?

 解题思路:

x和y融合产生x和x+y,也就等价于 每次操作将y修改为x+y,x不变。我们的目的是将所有相同的符文修改掉使得每一种符文只出现一次,所以可以找出所有相同的符文,将其作为y修改为x+y,x取当前所有符文中的最大值

为什么取最大值?因为如果x取最大值,那么不管y为何值,x+y一定大于y 也就是一定大于所有符文中的最大值,这样我们生成的新符文一定不会存在与之相同的符文,也就是说我们每次操作一定可以使一个相同的符文变为不同。

难度:2颗星

#include <bits/stdc++.h>
using namespace std;
int n, ans;
bool st[100005]; // st数组记录每一种符文是否出现过,用于判断是否有相同符文

int main()
{
    cin >> n;
    for (int i = 0; i < n; i++)
    {
        int x;
        cin >> x;
        if (st[x])
            ans++;
        else
            st[x] = 1;
    }
    cout << ans;
    return 0;
}

F. 小A的魔法符文Ⅱ

这个题目也挺有意思的,同样是考察贪心与上题类似,就看有没有发现那个关键点,如果发现的话就会非常的简单。

题意:

输入给出n个符文,每次操作可以选择其中的x,y两个符文进行融合成x-y一个符文。问当融合到只剩一个符文时,数值最大为多少?

解题思路:

这个题目涉及数学,不太好解释,需要自行领会。

题目给出的相减操作可以相互之间抵消,得到想要的加法和减法,具有一定的规律性。

所以我们分类讨论,首先如果n=1,那么答案就是我们的输入;

否则如果数组中既有正数又有负数,我用 a 表示任意正数,b 表示任意负数,我们一定可以构成一个这样的式子:a - ( b - a - a ……) - b - b……,其中第一个省略号省略所有的正数,第二个省略所有的负数,这样的目的是加上所有的正数减去所有的负数,最终所有数的绝对值之和就是答案;

否则如果数组中全为正数或全为负数,可以分别构造这样两个式子:a - ( a - a - a……)b - b - b……,这样的目的是使所有数中最终答案为 所有数的绝对值之和减去其中一个数的绝对值,所以除掉绝对值最小值外的所有数的绝对值和减去绝对值最小值就是最大解。

难度:3颗星

#include <bits/stdc++.h>
using namespace std;
int n, ans, mn = 1000, x;
bool st[2];

int main()
{
    cin >> n;
    for (int i = 0; i < n; i++)
    {
        cin >> x;
        ans += abs(x);
        mn = min(mn, abs(x));
        if (x < 0)
            st[0] = 1;
        else
            st[1] = 1;
    }
    if (n == 1)
    {
        cout << x;
        return 0;
    }
    if (st[0] && st[1])
        cout << ans;
    else
        cout << ans - 2 * mn;
    return 0;
}

G. jeazim的正方形

这个题也是我命题的,设计考察点是贪心+模拟+二进制优化

题意:

有一个由n*m个单位小正方形构成的矩形,可以通过一种合并操作将4个正方形合并成一个较大的正方形,需要找到一种最有策略使得最后剩余的正方形数量最少,输出最后的正方形数量。

解题思路:

首先我们考虑怎样的策略是最优的,也就是考虑贪心模拟的过程。

我们试图去找找规律,首先将所有的边长1正方形从左到右从上到下合成边长2正方形,然后此时可能会剩余一定数量的边长1正方形,我们发现这些剩余的边长1正方形都是无法参与后续的合并的,因为后续边长4正方形只能由边长2正方形生成。而那些参与了合并的边长1正方形,变成了边长2正方形,同时正方形数量减少3。合成的边长2正方形可以继续通过这种方式产生边长4正方形。我们可以以此类推,直到没有正方形可以合并。

如果我们单纯的模拟,每次一个一个合并正方形,这个时间复杂度会很高很高,如果这样也就会tle(时间超限)。这时候就需要优化一下,我们可以不必模拟出过程,只需要求出结果,类似二进制,每一次除以2,具体请结合代码理解。

难度:3颗星

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

int main()
{
	ios::sync_with_stdio(false);
	cin.tie(0),cout.tie(0);
	int T;
	int n, m;
	cin>>T;
	while(T--){
		cin>>n>>m;
		int ans = n * m;
		while(n >= 2 && m >= 2)
		{
			n = n / 2;    // 除以二下取整,相当于直接将剩余没合并的正方形不纳入后续考虑
			m = m / 2;
			ans = ans - n * m * 3;  // 每次减去合并减少的正方形数量
		}
		cout<<ans<<'\n';
	}
	return 0;
}

H. S级技能

这个题乍一看还是比较难的,不过理解思路后还是很清晰。考察点主要是暴力枚举

题意:

交易市场有n本技能书,其中玩家买入的价格每本分别为a[i],而出售的价格每本固定为s,玩家可以自行买入和卖出,问通过这种买卖机制如果玩家最初拥有d元能最多买入多少本书。

解题思路:

首先我们知道如果想拥有更多的钱,那么肯定是要买入尽可能便宜的然后出售。题目的难点就在于我们不知道与不好理解 最终应该买入那些书可以使得书的数量最多。

其实题目无疑是告诉我们两种操作:

  1. 对一本书同时买入和卖出,用于获取收益,积累资金;
  2. 对一本书只买入不卖出,用于积累书本数量。

但是我们并无法知道最优策略中哪些书应该进行第一种操作,哪些应该进行第二种操作。我们不好理解,也无法推出,而且题目给的数据范围很小,那我们不妨一个一个来枚举。枚举什么呢?枚举哪些书用来盈利,哪些书作为最后买入。

易知我们每次买入的书一定是剩余书中价格最低的最划算,题目的书价格是按顺序给出的,从小到大,所以我们就从前往后买知道买不起为止,我们枚举一个中间点,这个点之前的表示进行第一种操作,之后的表示进行第二种操作,计算直到买不起我们可以买到多少本书(即进行第二种操作的书有多少本)。最后将所有枚举点求出的值取一个最大值就是答案。这样讲可能有点抽象,具体结合代码理解

难度: 3颗星

#include <bits/stdc++.h>
using namespace std;
int n, d, s, ans;
int a[1005];

int main()
{
    cin >> n >> d >> s;
    for (int i = 1; i <= n; i++)
        cin >> a[i];
        
    for (int i = 1; i <= n; i++)   //第一个循环用来枚举中间点,i之前的点用来盈利,之后的点只买入不卖出
    {
        if (d < a[i])   // 费用不够买入,跳出循环
            break;
        d +=  s-a[i];     // 将书卖出获得利润
        int m=d,cnt = 0;   // m暂存当前所拥有的金币书,cnt计数器用于记录买入书的数量
        for (int j = i + 1; j <= n; j++)  //第二个循环用来求最多能买入多少本书
        {
            m -= a[j];
            if (m < 0)   //无法买入,跳出循环
                break;
            cnt++;
        }
        ans = max(cnt, ans);
    }
    cout << ans;

    return 0;
}

 I. 小A想要斗地主

这个题虽然题目很长,问题数量多,但每个问题都是非常基础的程序设计问题,所以实际难度是很低的。

题意:

前面的题目描述可以不用管,只需要知道牌面从大到小依次是2、A、K、Q、J、10(S)、9、8、7、6、5、4、3。然后直接回答下面7种牌能否打出:

解题思路:

很简单,自己用自己的想法模拟一下就行,也可以参考我的写法。由于牌面大小关系和数字大小不同,而我想按照牌面大小顺序存储每一张牌的数量,所以我用cnt数组存储每一种牌的数量,用mp数组作为存储cnt数组中牌面和下标映射关系;然后我用st数组表示7种牌型是否能打出;num数组记录i张牌连续的次数。下面是具体代码

难度:2.5颗星

#include <bits/stdc++.h>
using namespace std;
int cnt[15],num[4];
char str[60], mp[15] = "2AKQJS9876543";
bool st[8];

int main()
{
    int n;
    cin >> n >> str;
    for (int i = 0; i < n; i++)
        for (int j = 0; j < strlen(mp); j++)
            if (mp[j] == str[i])
                cnt[j]++;
    for (int i = 0; i < strlen(mp); i++)
    {
        if (cnt[i] == 0)
            num[1] = num[2] = num[3] = 0;
        if (cnt[i] >= 1)
        {
            st[1] = 1;
            num[1]++;
            if (num[1] == 5)
                st[5] = 1;
            if (cnt[i] == 1)
                num[2] = num[3] = 0;
        }
        if (cnt[i] >= 2)
        {
            st[2] = 1;
            num[2]++;
            if (num[2] == 3)
                st[6] = 1;
            if (cnt[i] == 2)
                num[3] = 0;
        }
        if (cnt[i] >= 3)
        {
            st[3] = 1;
            num[3]++;
            if (num[3] == 2)
                st[7] = 1;
        }
        if (cnt[i] == 4)
            st[4] = 1;
    }
    for (int i = 1; i <= 7; i++)
        cout << st[i] << ' ';
    return 0;
}

 J. 金铲铲

该题目用来作为我命题中最难的题,只考察了思维能力,未具有任何特定算法。如果考虑清楚,代码不长。该题具备一定的防AK能力,最后没有一个同学做出来也是符合我的预期的,虽然我出题时设想的是最好能有一两位优秀的同学能够解出来,毕竟该题并未考察特定算法。

以金铲铲为基础主题 的该题共设计产出了两个版本,这次比赛只选取了其中的hard版作为压轴出场

题意:

商店有1,2,3,4的棋子无数个,需要 求n金币能够刚好买入的不同阵容有多少种。值得注意的是,(由于1费棋子在游戏中表现弱势)题目限制了2费棋子的数量一定严格大于1费棋子的数量;(由于海克斯科技的作用)每买入3个4费棋子(三合一,获得奖励)可以获得一个2费棋子。

难度:5颗星

解题思路:

该题考察我们对算法的优化能力,那我们一步一步来,这里给出五种算法思路:

算法一:

由于4费棋子的特殊性,我们首先枚举当前方案中4费棋子的数量,如果我们继续枚举3费棋子的数量、2费棋子的数量以及1费棋子的数量,可以得到一个暴力解法,使用4层循环,时间复杂度为O(n^4)。

#include <bits/stdc++.h>
#define int long long
using namespace std;
signed main()
{
    int n, ans = 0;
    cin >> n;
    for (int k = 0; k <= n; k++) // 枚举4费棋子的数量
    {
        for (int s = 0; s <= n; s++)  //枚举3费棋子的数量
        {
            for (int j = 0; j <= n; j++)  //枚举2费棋子的数量
            {
                for (int i = 0; i <= n; i++)  //枚举1费棋子的数量
                {
                    if (i < j + k / 3 && k * 4 + s * 3 + j * 2 + i == n)
                    {
                        ans++;
                    }
                }
            }
        }
    }
    cout << ans << '\n';
}
算法二:

然后我们稍加优化,发现3费棋子由于没有任何限制条件与特殊性质,我们可以不用枚举,直接用总金币数减去4费2费1费棋子的消耗求出,也就是变成了三层循环,这样我们的算法时间复杂度变成了O(n^3)

#include <bits/stdc++.h>
#define int long long
using namespace std;
signed main()
{
    int n, ans = 0;
    cin >> n;
    int tmp = ans;
    for (int k = 0; k <= n / 4; k++)  //枚举4费棋子的数量
    {
        int m = n - k * 4;
        for (int j = 0; j <= m / 2; j++)  //枚举2费棋子的数量
        {
            int num = m - j * 2;
            for (int i = 0; i <= num; i++)  //枚举1费棋子的数量
            {
                int t = num - i;   //剩余金币全部买3费棋子
                if (i < j + k / 3 && t % 3 == 0)  //剩余金币全部买3费棋子必须满足能把金币花完的条件
                {
                    ans++;
                }
            }
        }
    }
    cout << ans << '\n';
}
算法三:

然后我们发现3,2,1这三个数非常的特殊,似乎存在着某种联系,是的,那就是3=2+1。(拓展知识:3,2,1这三个数关联颇深,在二进制下他们两两异或可以得到另一个数,这个特殊性也在很多地方得到使用)

 3=2+1有什么特殊的呢?

假设我们不考虑买入3费棋子,也就是所有的金币全买4,2,1费棋子,这样我们只要两层循环就能解决,第一层枚举4费棋子的数量,第二层枚举2费棋子的数量,而1费棋子的数量就等于剩余金币数。这时我们再来引入3费棋子,发现每一个2费棋子和一个1费棋子可以替换成一个3费棋子得到一种新的方案,这时2费棋子和1费棋子之间的较小值就是我们最多可以替换的3费棋子数量。也就是我们每次通过枚举4费棋子的数量和2费棋子的数量可以得到一定的方案数,而所有的方案数之和就是要求的总方案数。这样我们需要两层循环,时间复杂度为O(n^2)。

#include <bits/stdc++.h>
#define int long long
using namespace std;
signed main()
{
    int n, ans = 0;
    cin >> n;
    int tmp = ans;
    for (int k = 0; k <= n / 4; k++)  //枚举4费棋子的数量
    {
        int m = n - k * 4;
        for (int j = 0; j <= m / 2; j++)  //枚举2费棋子的数量
        {
            int num = m - j * 2;   //num表示剩余金币数,也就是1费棋子的数量
            if (num >= j + k / 3)   //如果2费棋子数量小于等于1费棋子数量,不符合条件
                continue;
            ans += min(num, j) + 1;   //将局部方案数加入总方案数
        }
    }
    cout << ans << '\n';
}
算法四:

继续寻找规律,我们发现,随着我们枚举2的数量,ans加入的值实际上满足等差数列,那我们应该只要求出等差数列求和的公式即可得出答案。

————————

算法五:

你以为做完了,不,这个数列其实并不是单调的等差数列!所以并不能用一个公式直接计算得出。由于3个4费会赠送一个2费,题目并未结束,因为3个4费赠送的2费是不能用来与1费一起被3费取代,所有随着购买的2费数量的增加,刚开始数列是递增的等差数列,然后到达某个临界值之后会递减!没想到吧。这是这个问题优化步骤中最难的地方,也是体现了两个限制条件中3个4费免费赠送一个2费 与 2费棋子数量必须严格大于1费棋子数量 之间的一个巧妙的联系

如何求这个临界值可以结合代码领会,我这里就不再解释了,这里不是很好讲清楚以后有时间再写吧(哭)

这一步优化会比较难理解,也是最容易出错的一步

最终解题代码(纯代码版):

#include <bits/stdc++.h>
#define int long long
using namespace std;
signed main()
{
    int n;
    int ans = 0;
    cin >> n;
    for (int i = 0; i * 4 <= n; i++)
    {
        int num = n - i * 4 + i / 3 * 2, t;  
        if (num == 0)
            continue;
        int l2 = max(num / 3 + 1, i / 3), r2 = num / 2;
        if (l2 * 2 > num)
            continue;
        int r1 = num - l2 * 2, l1 = num - r2 * 2;
        t = num - i / 3 * 2;
        int t2 = t / 3;
        int t1 = t - t2 * 2;
        if (i >= 3)
            ans = ans + (t2 - l2 + i / 3 + 1) * (t2 + l2 - i / 3 + 2) / 2;
        t2++;
        t1 -= 2;
        ans += ((t1 - l1) / 2 + 1) * (t1 + l1 + 2) / 2;
    }
    cout << ans << endl;

    return 0;
}

K. 花花花

待更新完成——

结语

总的来看,这些题目代码量都非常的少,特别是就连目前完成解题的压轴之一J题也不过短短30行,I题共7个问题也只有50行。

有点忙,暂时没什么说的,那祝大家天天开心,事事顺利吧(*^▽^*)

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值