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

2022湖南科技大学 新生快乐赛

按当时的过题人数降序写一下题解吧
新生可以根据一些不懂的地方学下一下
大佬自行绕路

(本题解都是用C++写的 ,由于 懒得写两份 C++是竞赛常用语言,所以如果有看不懂的就去学一下C++的语法吧,反正以后用的着 )

tips: 要是有些题目感觉我说的不清楚可以找一下 @ jeazim 的博客(你们的张志学长 他还写了c版本的题解)


H 欢迎来到泰拉瑞亚,你准备好了吗?

题意:输入一个字符串,判断是否字符串中是否存在给定的字符串(不区分大小写)

签到题。直接模拟即可 可以二重循环一个个判断

  • 建议:由于不区分大小写,可以将所有字符都转化为小写的
#include <bits/stdc++.h>
using namespace std;
 
int main(){
    int n;
    string s;
    string t = "terraria";
    cin >> n >> s;
    for (auto &x : s){
        x = tolower(x);
    }
    if(s.find(t)!=string::npos) cout <<"Yes\n";
    else cout <<"No\n";
    return 0;
}

B 卷王日记

题意:输入一个字符串 X则灯关上,Q则灯状态改变
原来弄的题是可以3Q 也可以Q2Q这种,那就是直接模拟,后来改了只能3Q这种形式,那么X和Q一定是交叉出现的 所以只要分类讨论一下最后面出现的字符就可以了 (这里有个疑惑,是否X至少会出现一次 从题面里的描述可以说明是这样的 故可以默认灯至少会被关上一次)

#include <bits/stdc++.h>
using namespace std;
 
typedef long long ll;
 
int main() {
    int n;
    string s;
    cin >> n >> s;
    if(s[n-1]=='Q'){
        if(s[n-2]=='X'||(s[n-2]-'0')&1 )cout << "yes\n";//若最后出现了奇数次Q 
        else cout <<"no\n";
    }
    else cout <<"no\n";
    return 0;
}

A 周小美大人是会长

题目大意:n个字符任选两个下标为 i , j ,可以重复选 ,i,j中有任意一个不相等就算不同方案。求满足s[i]==s[j]的方案数
思路: char类型ASCLL码最多到128 直接开个数组映射,记录每个字符出现的次数

能记录下来,就比较简单了 ,计算方法有很多种,我这份代码可以理解为:
i和j相等的时候肯定是答案 +n
对于每个字符 s[i] ,在i之前出现的字符下标都可以作为j
所以加上之前出现过的次数 由于是排列 所以i和j互换乘以2

#include <bits/stdc++.h>
using namespace std;
 
typedef long long ll;
 
int cnt[129];
 
int main() {
    ll n , res = 0;
    string s;
    cin >> n >> s;
    for (int i=0; i<n; ++i){
        res += cnt[s[i]]++;
    }
    cout << 2*res + n <<"\n";
    return 0;
}

D: 心之钢?心之钢!

题意:技能对每个敌人的CD30s(独立计算CD),每次使用可以增加自身一定生命值
模拟即可 ,细节见代码

#include <bits/stdc++.h>
using namespace std;
 
typedef long long ll;
 
int main() {
    double a,b;
    int t , res = 0;
    cin >> a >> b >> t;
    a += 800;
    for (int i=1; i<=b; ++i){
        int x;
        cin >> x;
        if(x<=t){
            res += 1 + (t-x)/30;
        }
    }
    for (int i=1; i<=res; ++i){
        a = a + 12.5 + a*0.004;
    }
    printf("%d %.2f\n", res,a);
    return 0;
}

C 卷王日记(二)

题意 给定一个地图,求初始点到有效点的最短曼哈顿距离

(曼哈顿距离——两点在南北方向上的距离加上在东西方向上的距离,即d(i,j)=|xi-xj|+|yi-yj|)

这题最开始数据是1e4 时限1s 有点卡常 后面放开了

分析:可以把中间的走廊也看成一列 然后走廊右边的列坐标都加一 那么你自己就也有一个坐标啦!
大概长这个样子 =》画了个什么破烂图 看懂就行
**在这里插入图片描述**
这时候你只要遍历所有点,然后记录最小的曼哈顿距离就ok了
代码:

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

struct node
{
    int cnt;
    int res;
};

int main() {
    ios::sync_with_stdio(false),cin.tie(nullptr);
    cout.tie(nullptr);

    int n,m;
    char ch;
    cin >> n >> m;
    int cnt = 0;
    int stx = 0,sty = m/2+1;//你自己站的位置

    node ans = {0,(int)1e9}; //初始化为无穷 表示没有有效位置  

    for(int i=1; i<=n; ++i){
        for(int j=1; j<=m/2; ++j){
            cnt++;
            cin >> ch;
            if(ch=='F'){
                int t = abs(stx-i)+abs(sty-j);
                if(t<ans.res)ans = {cnt,t};
            }
        }
        for(int j=m/2+2; j<=m+1; ++j){
            cnt++;
            cin >> ch;
            if(ch =='F'){
                int t = abs(stx-i)+abs(sty-j);
                if(t<ans.res)ans = {cnt,t};
            }
        }
    }

    if(ans.res==1e9){
        cout <<"0 0\n";
        return 0;
    }

    cout << ans.res <<" " << ans.cnt <<"\n";
}

I 神,不惧死亡!

题目大意: 有一个数组 f[N]
f[1] = a ,f[2] = b,f[i] = f[i-1] + f[i-2] (i>=3)
当前你有x个宇宙锭,你可以支付 T个(T<=x)宇宙锭来获得 f[T] 的价值。
q个询问,告诉你当前你有的宇宙锭的数量,问获得的价值的最大值。答案对998244353取模
场外看的时候一直不知道为啥这题没人做?? 后来想了一下,可能对取模这个概念还不是很了解??或者还有一些我看了是忽略了 x等于2的时候可以支付一个也可以支付两个 没有比较a和b的大小。
本题要用到的公式:

(a + b) % p = (a % p + b % p) % p

以及,这个公式可以推广到n个数字相加,只要每次都取个模就行了。
还有其他的取模公式自行百度,这里不介绍了。 思路和下一题差不多,打表预处理。

由于每个贡献都是正的 故对所有大于等于3的都是严格单调递增的,直接宇宙锭全给了就是答案,x等于2就是 max(a,b) .

#include <bits/stdc++.h>
using namespace std;
 
const int mod = 998244353;
const int Maxn = 2000001;
 
int v[Maxn];
 
int main(){
    int q ,a,b;
    cin >> a >> b >> q;
    v[1] = a;
    v[2] = b;
    for (int i=3; i<Maxn; ++i)v[i] = (v[i-1] + v[i-2]) % mod;
    while (q--) {
        int x;
        cin >> x;
        if(x==2)cout << max(a,b) <<"\n";
        else cout << v[x] << "\n";
    }
    return 0;
}

F 一起去旅行

题意:可以往前走x步,往后走y 步 (y<x) q个询问问哪些位置可以走到

题意说一开始一定往前走x,而且每次后退了y以后,下一步一定是走x 。其实就是告诉我们y和x是绑定的,因为只要出现一次y,那么事先一定有一次x 。于是问题可以转化为:可以往前走a步或者b步 (b=x-y ,a = x , a>0 ,b >0 ,a>b).
我们可以发现能否到达X的充要条件是,到达X-a或者X-b 换句话说,X的状态取决于X-a和X-b的状态 。那么X-a能不能到呢? 似乎就取决于X-a-a和X-a-b?那么直接用一个dp数组存储,dp为1表示可以到这个地方,dp为0表示不能 ,那么如何转移呢?事实上,我们发现计算x所需要的状态都是小于x的!也就是说,每一个地方计算的状态都可以由前面的一个或几个状态推出来。 故我们只要 for循环从小到大 顺序计算所有需要的状态即可。

即:

dp[i] = dp[i-x]||dp[i-x+y];

完整代码:

#include <bits/stdc++.h>
using namespace std;
const int N = 1e7+50;
 
int dp[N];
 
signed main()
{
    ios::sync_with_stdio(false);
    cin.tie(nullptr),cout.tie(nullptr);
 
    int n,x,y;
    cin >> n >>  x >> y;
    dp[x] = 1;
    dp[x-y] = 1;
    for(int i=x+1; i<=1e7+1; ++i)dp[i] = dp[i-x]||dp[i-x+y];
    while(n--){
        int c;
        cin >> c;
        if(dp[c])cout <<"yes\n";
        else cout<<"no\n";
    }
 
    return 0;
}

G 破碎的音符

感觉和某谷 合并果子有点类似,感兴趣可以去某谷做一下 题号 P1090
思路:
将拼图分为 中间和左右 三部分
由题意可知 越先合并的 累加的次数越多
对答案的贡献就越大 由于求最小答案
故每次贪心取最小的两个合并即可
求最小的可以用优先队列,也可以暴力写

细节在代码和注释里了

#include <bits/stdc++.h>
using namespace std;
 
#define int long long
 
void solve()
{
    int n,l,r;
    cin >> n >> l;
 
    // 优先队列  原理是堆排序  可以logn时间取出堆中最小元素 logn时间插入元素
    // 没了解的可以直接O(n)暴力 遍历所有元素找最小 也能通过
     
    priority_queue<int, vector<int>, greater<int> > q;
     
    for (int i = 2; i < n; ++i) {//中间部分加入优先队列
        int x;
        cin >> x;
        q.push(x);//加入优先队列
    }
     
    cin >> r;
 
    int res  = 0;
     
    if (q.size() == 0)res = l + r ;
    else {
        //每次贪心从三部分中选两个元素合并
        //注意 中间部分元素可以多个  左右只有一个元素 模拟这个过程即可
        while (q.size()) {
            if (q.size() == 1) {
                if (l + q.top() < r + q.top()) {//判断如何合并是最优
                    l += q.top();
                    res += l;
                } else {
                    r += q.top();
                    res += r;
                }
                q.pop();
            }
            else {
                int a = q.top();
                q.pop();
                int b = q.top();
                q.pop();
                //避免分类讨论,直接swap保证a<=b
                if (a > b)swap(a, b);
                if (a + b < a + l && a + b < a + r) {//判断如何合并是最优
                    res += a + b;
                    q.push(a + b);
                }
                else {
                    if (l + a < r + a) {//判断如何合并是最优
                        l += a;
                        res += l;
                    } else {
                        r += a;
                        res += r;
                    }
                    q.push(b);
                }
            }
        }
        res += l + r;//最后将所有部分合成一个
    }
    cout << res << "\n";
}
 
 
signed main() {
    ios::sync_with_stdio(false), cin.tie(nullptr);
    cout.tie(nullptr);
 
    int t = 1;
    cin >> t;
    while (t--) {
        solve();
    }
    return 0;
}

I 神,不惧死亡!

防AK
其实是个 线性基纯模板题。
原理我 也不会 就不说了,网上可以找到各种题解 我就不当小丑了

代码:

#include <iostream>
#include <algorithm>
using namespace std;

const int MAX = 1e5+5;
typedef long long ll;
ll arr[MAX];
ll p[MAX];
 
void Get_LB(ll x){
    for(int i = 62; i >= 0; --i){
        if(!(x >> (ll)i))
            continue;
        if(!p[i]){
            p[i] = x;
            break;
        }
        x ^= p[i];
    }
}
int main()
{
    ios::sync_with_stdio(false);
    cin.tie(0); cout.tie(0);
    int n;  
    cin >> n;
    for(int i = 0; i < n; ++i){
        cin >> arr[i];
        Get_LB(arr[i]);
    }
    long long ans = 0;
    for(int i = 62; i >= 0; --i){
        if( (ans ^ p[i]) > ans)
            ans ^= p[i];
    }
    cout << ans << endl;
 
    return 0;
}

J 旅行者的涂色游戏

防AK
进阶博弈论题
前置知识:SG函数、公平组合游戏、SG定理、必败态和必胜态。
由于全部介绍篇幅较长,有兴趣的可以自行百度。

题意: 二人对弈,初始一个1×n的方格 可以涂红色也可以涂蓝色,但是相同颜色不能相邻。 初始时有m个位置已经涂上了颜色,数据保证初始位置不会出现相同颜色相邻的问题 。 轮流涂色,当有一人无法继续涂色时,游戏结束,无法涂色的人判负。 问:在两人都是最优策略的情况下,谁是胜者?

这是一个公平游戏,两人的博弈目标都是相同的,并且我们发现涂色部分可以把方格分割成若干段。那么每一段空白棋盘的sg函数的异或和是否为0可以 判断先手是否必胜。

现在的问题就是如何求每一段的SG函数:

我们发现可以为以下四种情况:
Case 1:
所有地方初始都没有涂上颜色。
这时胜负取决于n的奇偶性,若n为奇数,先手可以对中间的方格涂色,此时棋盘分为两个对称的部分,接下来无论对方怎么涂色,我们都可以构造出一个相同颜色对称的局面。
如图:第一次涂色在8这个位置 ,则对方无论涂在哪个位置,若对方合法,关于8这个点的对称位置也合法,故最终一定是先手胜利。
在这里插入图片描述
反之,若n为偶数,则后手可以根据中间点来构造一个相反颜色对称的局面,最终一定是先手无法操作。
在这里插入图片描述

即:这种情况的SG函数为 n%2 ,而在本题中,若出现这种情况,则只划分了一个区域,也就是只有一个SG函数 故n的奇偶直接决定最后的胜者。

Case 2:
一端涂上了颜色,而另外一端没有

对于这种情况,显然一端的颜色是红是蓝不会影响其结果,这里先给出结论:这种情况下的sg函数等于空白的长度,因为推导这个结论需要结合Case 3和Case 4推导。

Case 3
两端的被同一种颜色覆盖

显然两端的方格是红是蓝我们也不需要考虑,我们直接假设都为红色。此时显然的条件是:当长度为奇数时候,先手一定可以构造一个对称局面(相当于case1的奇数情况),**我们在正中间下一个棋,接下来就是模仿对手,显然是必胜的,**其sg函数恒为1。但是对于偶数情况,我们还不得知。后续推导

Case 4
两端的被不同种颜色覆盖

不妨设两端棋子一端是红一端是蓝,当空白长度为偶数的时候,后手可以构造一个相反对称棋盘类似Case1的后手对称,其sg函数为0(相当于case1的偶数情况)。但是对于奇数情况,我们也不得而知。

我们发现,这几种还未推出来的情况本身也可以看做一个新的对弈。可以分成几部分

对于Case 3长度为偶数的情况,也就是两端都是相同颜色,中间的长度为偶数。我们可以在最左端(或者最右端)的隔开一个格子的位置上涂上一个不同颜色,那么隔开的这个位置不能填任何棋子了,而另一边就形成了Case4的必败态,那么Case3不管奇偶都是必胜态,其sg函数为1。

对于Case 4长度为奇数的情况,也就是一端为红一端为蓝,此时先手不管怎么下这个棋,一定会产生两端颜色一样的必胜态,而另一端就还是不同颜色的局面,先手无论如何无法打破这个局面,直到左边这段下不了。因此,Case 4永远是先手必败,其sg函数为0。

我们现在还有Case 2的sg函数没有分析,不管我们怎么下,一定会产生一端Case3或者Case4,然后和另一段Case 2继续异或,那么对于一个长度为n的空白段,可以转移到0, 1, 2, 3, 4…n - 1,也就是说其sg函数等于长度n。

至此,我们只要把m个初始颜色划分的段逐个求出对应的SG函数,然后求他们的异或和即可。时间复杂度 O(m)

代码:

#include <iostream>
using namespace std;
 
#define int long long 
#define endl "\n"
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" << "\n";
        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;
}

希望这个题解能给到一定帮助,祝各位学弟新生赛取得好成绩!! 不要像我一样太混了,当时新生赛的时候甚至不知道ACM赛制是什么 (逃~

  • 3
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

早柚爱睡觉

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值