2022湖南科技大学-新生快乐赛部分题题解

目录

A.周小美是学长大人

题目描述:

解题思路:

具体过程:

B.卷王日记

题目描述:

解题思路:

C.卷王日记(二)

题目描述:

解题思路:

具体过程:

G.破碎的音符

题目描述:

解题思路:

具体过程:

F.一起去旅行

题目描述:

解题思路:

J.旅行者的涂色游戏

题目描述:

解题思路:


A.周小美是学长大人

题目描述:

指定一字符串s,求出字符串中相同字符对的个数。

即对于 i, j(0<=i,j<n)有s[i]==s[j];

注意这里没有i!=j的限制,也就是说(1,1)也属于题目中的字符对,且(1,2),(2,1)是不同的字符对

解题思路:

这题简单,直接循环找相同的个数

#include <stdio.h>
#include <string.h>

int main()
{
    char s[100005];
    int n,cnt=0;
    scanf("%d%s",&n,s);
    for(int i=0;i<n;i++)
    	for(int j=i;j<n;j++)
    		if(s[i]==s[j])cnt++;
    printf("%d",cnt);
    return 0;
}

结果发现时间超限!!!

这个题目显然不能直接求,我们思考一下怎样直接算出字符对的个数:

假设我们已经知道某个字符在字符串的数量为m,则这个字符能够产生的字符对为m*m(i的m种取法*j的m中取法)。比如样例中的zxm666,字符对总数应该为1*1+1*1+1*1+3*3=12.

具体过程:

#include <stdio.h>
char s[100005];
long long sum, cnt[150], n;

int main()
{
    scanf("%d%s", &n, s);
    for (int i = 0; i < n; i++)
        cnt[s[i]]++;   //cnt数组记录每个字符出现的次数,通过ascii码映射
    for (int i = 0; i < 150; i++)  //范围随便取,只要能包括题目字符中的ascii码范围就行
        sum += cnt[i] * cnt[i];  //公式
    printf("%lld", sum);
    return 0;
}

B.卷王日记

原题点这里!

题目描述:

有两种开关,一种按一下会在灯泡亮时让灯泡熄灭,在灯泡灭时让灯泡打开;另一种无论灯泡为什么状态都会让灯泡熄灭,根据题意字符串中必定会出现X且灯泡初始状态为开。模拟两种灯泡的快关情况,判断最终灯泡是否还开

解题思路:

这原本是一个思维题,对于一个字符串,其实只需要考虑最后两位即可得出答案。

为啥呢?

我们考虑几种情况:如果最后一位为X,那灯必定熄的;如果最后一位是Q且倒数第二位是一个偶数,则灯也是熄的;其余情况均为亮。

很简单,直接上代码

#include <stdio.h>
int n;
char ch[1000005];

int main()
{
    scanf("%d%s", &n, ch);
    if (ch[n - 1] == 'X')//判断最后一位
        printf("no\n");
    else if (n > 1 && ch[n - 2] != 'X' && (ch[n - 2] - '0') % 2 == 0)//这时最后一位是Q,如果倒数第二位是偶数,那么灯必定是关的
        printf("no\n");
    else
        printf("yes\n");
    return 0;
}

没错,一个if条件句就可以解决。

这个题目其实直接模拟完全也是可以过的,不愧是签到题哈哈

//c语言代码这里就不放了
#include <cmath>
#include <iostream>
using namespace std;

int main()
{
    int n, flag = 0, x = 1;
    char ch;
    cin >> n;
    for (int i = 0; i < n; i++)
    {
        cin >> ch;
        if (isdigit(ch))//处理数字
            x = ch - '0';
        else           //处理字母
        {
            if (ch == 'X')
                flag = 0;
            else
                flag ^= x % 2; //只需要看奇偶就行啦
            x = 1;
        }
    }
    if (flag)
        cout << "yes" << endl;
    else
        cout << "no" << endl;
    return 0;
}

代码内容可以理解一下,大佬们可以来补充一下c语言代码

C.卷王日记(二)

原题点这里!

题目描述:

简而言之就是求出到初始点曼哈顿距离最短的点的距离及其编号。

解题思路:

这个题目考察空间想象能力,认真想想会发现,这就是相当于把xy坐标轴向右平移了一下。平移后题目就转化成了求点到坐标原点的距离,我们只要对每一个空闲的位置求出距离,然后取最小值即可。

具体过程:

看代码!此代码为小玉同学提供!c++版

#include<iostream>
#include<string>
#include<algorithm>

using namespace std;

const int N = 1e4+100;

#define LL long long

int n,m;
char g[N][N];

int main()
{
    scanf("%d%d", &n, &m);
    if(!n || !m) 
    {
        printf("0 0\n");
        return;
    }
        
    for(int i=0; i<n; i++) scanf("%s", g[i]);
    
    LL min_num = -1, min_distance = 2e9;
    for(int i=0; i<n; i++)
    {
        for(int j=0; j<m; j++)
        {
            LL num = (LL)i*m + j + 1;
            //printf("%d ", num);
            if(g[i][j] == 'F')
            {
                LL now_distance;
                if(j < m/2) now_distance = 2 + i + abs(j+1-m/2);
                else now_distance = 2 + i + abs((m/2+1)-(j+1));
                if(now_distance < min_distance) 
                {
                    min_distance = now_distance;
                    min_num = num;
                }
                //printf("%d ", now_distance);
            }
            
        }
        //puts("");
    }
    if(min_num == -1) printf("0 0\n");
    else printf("%lld %lld\n", min_distance, min_num);
    return 0;
}

理解题目意思之后比较简单,只要求出最短距离的点即可。

鉴于c++代码可能看不明白,这里专门写了一个c语言代码帮助大家理解,主要是解题的思维。

#include <stdio.h>

int main()
{
    int n, m, minx = 0x3f3f3f3f, flag = 0;
    char ch[10005];
    scanf("%d%d",&n,&m);
    int len = m / 2;
    for (int i = 1; i <= n; i++)
    {
        scanf("%s",ch+1);    //在这里以字符串形式输入可以让读入效率更高
    	for (int j = 1; j <= m; j++)
        {
            if (ch[j] == 'F')   //当位置空闲时进行计算
            {
                if (j > len && minx > i + j - len)  //座位在图书馆右侧
                {
                    minx = i + j - len;
                    flag = (i - 1) * n + j;
                }
                else if (j <= len && minx > i + len - j + 1)  //座位在图书馆左侧
                {
                    minx = i + len - j + 1;
                    flag = (i - 1) * n + j;
                }
            }
        }
	}
    if (flag)
    	printf("%d %d\n",minx,flag);
    else
        printf("0 0\n");
    return 0;
}

这个题目必须给大家道个歉(真的非常非常抱歉),首先是数据有问题,然后是时间超限的问题,这个题目会因为输入次数太多导致时间超限。

数据问题题目已经进行过重审,如果当初提交时是对的但是误判了的已经判为ac。

减少输入次数的方法:每行输入一个字符串代替每个位置输入字符

G.破碎的音符

原题点这里!

题目描述:

有一个只有一行的拼图,每一次拼接都需要消耗特定数量的音符,问最少需要消耗多少音符。

这里注意拼图的最左边和最右边位置是固定的,但其他的拼块可以随意放在中间的任何位置。

解题思路:

首先需要用到贪心的思想:要想让总的音符消耗最少,需要每次取出两个音符消耗最少的拼块来拼接,类似于哈夫曼树

本题主要难点在于怎样使拼图的两边位置不变,我们可以对每一个拼块做一个标记,其中左右两边的拼块标记为0,中间的拼块标记为1.

 接下来是题目的关键,很容易理解:

标记为1的拼块可以相互随意拼接,标记为0的拼块可以与标记为1的拼块拼接,在拼图的最后一步进行0和0的拼接。注意,如果在过程中进行了0和0的拼接,其它拼块将不能继续拼接!

具体过程:

先将拼块做好标记,每次取出最小的两块拼块。之后需要分类讨论:如果两个拼块标记都为1,则形成新的标记为1的大拼块;如果一个拼块为0,一个拼块为1,则形成标记为0的大拼块(因为新的拼块变成了边界);如果两个拼块标记都为0,若此时只有两个拼块则完成最后拼接,若多于两个拼块则需取出第三小的拼块并重复上述过程。

我们直接上代码:

#include <iostream>
#include <queue>
#include <vector>
#define v first
#define w second
using namespace std;
typedef long long LL;
typedef pair<LL, bool> PLB;
priority_queue<PLB, vector<PLB>, greater<PLB>> q;//优先队列维护拼块结构
int n;

void solve()
{
    cin >> n;
    long long sum = 0;
    q = priority_queue<PLB, vector<PLB>, greater<PLB>>();
    for (int i = 1; i <= n; i++)
    {
        int x;
        cin >> x;
        q.push(make_pair(x, i != 1 && i != n));
    }
    while (q.size() > 2)//当拼块数大于二时取两个最小拼块
    {
        auto a = q.top();
        q.pop();
        auto b = q.top();
        q.pop();
        if (!(a.w || b.w))//分类讨论过程
        {
            auto c = q.top();
            q.pop();
            q.push(b);
            b = c;
        }
        LL num = a.v + b.v;
        sum += num;
        q.push(make_pair(num, a.w && b.w));
    }
    auto a = q.top();
    q.pop();
    auto b = q.top();
    q.pop();
    sum += a.v + b.v;
    cout << sum << endl;
    return;
}

int main()
{
    ios::sync_with_stdio(0);
    cin.tie(0);
    cout.tie(0);
    int t;
    cin >> t;
    while (t--)
        solve();
    return 0;
}

鉴于c++代码可能看不明白,这里专门写了一个c语言代码帮助大家理解,主要是解题的思维。

#include <stdio.h>

long long a[10005];
int st[10005];

int main()
{
    int t;
    scanf("%d", &t);
    a[0] = 1e17L;
    while (t--)
    {
        // printf("1\n");
        int n;
        scanf("%d", &n);
        long long sum = 0;
        for (int i = 1; i <= n; i++)
        {
            scanf("%lld", &a[i]);
            st[i] = i;
        }

        for (int k = 1; k < n - 1; k++)
        {
            long long min1 = 0, min2 = 0, min3 = 0;//用来去三个最小拼块
            for (int i = 1; i <= n; i++)
            {
                if (st[i] != i)
                    continue;
                //这样可以求三个最小拼块
                if (a[min1] > a[i])
                {
                    min3 = min2;
                    min2 = min1;
                    min1 = i;
                }
                else if (a[min2] > a[i])
                {
                    min3 = min2;
                    min2 = i;
                }
                else if (a[min3] > a[i])
                {
                    min3 = i;
                }
            }
            //分类讨论
            if (st[min1] == 1 && st[min2] == n || st[min1] == n && st[min2] == 1)
            {
                a[min1] += a[min3];
                st[min3] = min1;
            }
            else
            {
                if (min2 == 1 || min2 == n)
                {
                    int temp = min1;
                    min1 = min2;
                    min2 = temp;
                }
                a[min1] += a[min2];
                st[min2] = min1;
            }
            sum += a[min1];
        }
        printf("%lld\n", sum + a[1] + a[n]);
    }
    return 0;
}

这个题目有一定难度,需要一定的思考

题目还有一个我自己也没想到的点需要特别注意,感谢cxz同学的反馈:当有多个消耗量相同的拼块存在时,应当优先选择标记为0的拼块,即排序时应将标记为0的拼块排在前面(使用小根堆)。如3 3 3 3 四个拼块,如果先将标记为1的中间两个拼块结合,两个标记为0的拼块将无法继续结合,导致答案偏大。

F.一起去旅行

原题点这里!

题目描述:

这个题目就有意思了,一般人看到会推出各种貌似正确的公式,然后利用公式看能否到达指定位置,对于所有这些,答案当然是否定的,方程无解

这里简单解释一下为什么会无法通过规律推出,设前进距离为a,后退距离为b,到达s点需要前进x次,后退y次,会有公式a*x-b*y=s,这是一个二元方程,二元方程x与y可以取任意值,而且x,y必须为整数。

解题思路:

这个题目如果直接下手的话,由于不能连续后退,会非常棘手,需要讨论很多问题。

题目的突破口在于:

每次后退之前必有一次前进,也就是说完全可以把每一次后退和它的前一次前进联系为一个整体!这样题目就变成了每一次前进可以选择前进 x 或前进 x-y ,此时便没有了后退必须前进的约束条件。

完成转换后,我们发现还是不能用前进 x 和前进 x-y 推出能否到达一个指定的点,原因与前面相同,这是一个二元方程。也就是说这个题目不能做到在线证明某个点是否能够到达,对于这种问题,我们一般采用预处理打表的方式。

什么是打表:

打表就是把程序在线处理转化为预处理。简单来说,就是用一个数组存储所有能到达的点,当询问某个点是否能到达时只需要判断概述是否在数组当中即可。

如何打表:

一个位置m是否能够到达取决于m-x和m-(x-y)是否能够达到,只要其中有一个是可到达的点,那么m必定能到达,我们可以根据这个推出递推公式。

   我们来看具体代码:

#include <stdbool.h>
#include <stdio.h>
bool st[10000005];//下标表示位置,当st[i]值为1时
int x, y, n;
int main()
{
    scanf("%d%d%d", &n, &x, &y);
    st[0] = 1;
    for (int i = 0; i <= 10000000; i++)//打表打表打表打表
    {
        if (i >= x) st[i] |= st[i - x];
        if (i >= x - y) st[i] |= st[i - x + y];
    }
    for (int i = 0; i < n; i++)
    {
        int s;
        scanf("%d", &s);
        if (st[s])printf("yes\n");//如果数组中存在,输出yes,否则输出no
        else printf("no\n");
    }
    return 0;
}

代码只有这么长哦。

J.旅行者的涂色游戏

题目描述:

派蒙来和你解释

解题思路:

这个题目我直接放代码吧,防ak题,看看就好。

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

#define int long long 

const int N = 2e5+50;

int n, m;
int X[N], Y[N];

signed main() {
    cin >> n >> m;
    for (int i = 1; i <= m; i++) {
        char ch;
        cin >> X[i] >> ch;
        if(ch=='R')Y[i] = 0;
        else Y[i] = 1;
    }
    X[0] = 0;
    Y[0] = 2;
    X[m + 1] = n + 1;
    Y[m + 1] = 2;

    if (m == 0) {
        if (n % 2 == 1) cout << "Aether" << endl;
        else cout << "Lumine" << endl;
        return 0;
    }

    int res = 0;
    for (int i = 0; i <= m; i++) {
        if (Y[i] == 2 || Y[i + 1] == 2) res ^= (X[i + 1] - X[i] - 1);
        else if (Y[i] == Y[i + 1]) res ^= 1;
        else res ^= 0;
    }
    if (res == 0) cout << "Lumine" << endl;
    else cout << "Aether" << endl;
    return 0;
}

写了一晚上呜呜,希望能对大家有所帮助!

特别提示:不要用gets! 不要用gets! 字符串输入可以直接用 scanf("%s",ch).

暂时只能写到这里,对自己的题了解一点,以后有机会再更新其他题吧,祝大家新生赛都取得好成绩!

评论 9
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值