【Acwing 周赛复盘】第90场周赛复盘(2023.2.11)

【Acwing 周赛复盘】第90场周赛复盘(2023.2.11)

周赛复盘 ✍️

本周个人排名:1488/2884

AC情况:1/3

这是博主参加的第五次周赛,这次做题的时候,感觉题目好难 😂

但是一听y总讲解,又感觉这次题目还是比较简单的,自己为什么就没想出来呢 😭

归根结底,还是自己太菜了,仍需不断努力。💪

T1 签到题,读懂题意即可AC。✅

T2 贪心,但是贪心策略以及边界条件有一些复杂,导致自己写代码时,思路很乱。有一种哪里都能下手,但是哪里又都不能下手的感觉,现场未 AC。❌ (后来看y总讲解后,把思路理清了,就顺利写出了代码)

T3 贪心+字符串,现场经过长时间的推导,发现了贪心策略以及前后缀,但是因为一些小错误(如判断 abcdcba,由于写的是 s[i] == s[n-i+1](是判断「首尾对称」的写法,而不是前后缀的判断),导致出现错误),现场未能AC ❌ (经过y总讲解后,觉得有必要学习一下 KMP 的相关内容)

image-20230222102506046


周赛信息 📚

时间:2023年 2 月 11 日 19:00-20:15

竞赛链接https://www.acwing.com/activity/content/2870/

y总直播间http://live.bilibili.com/21871779

y总录播讲解视频【AcWing杯 - 第 90 场周赛讲解】


题目列表 🧑🏻‍💻

题目名称原题链接视频讲解难度
4806. 首字母大写原题链接视频链接简单 🟢
4807. 找数字原题链接视频链接中等 🟡
4808. 构造字符串原题链接视频链接困难 🔴

题解 🚀

【题目A】首字母大写

【题目描述】

给定一个由大小写字母构成的单词。

如果单词的首字母为小写字母,则请你将该首字母转换为对应大写字母。

如果单词的首字母为大写字母,则不做任何变化。

输出最终的单词。

【输入】

一个由大小写字母构成的非空字符串,表示给定单词。

【输出】

输出最终的单词。

【数据范围】

3 3 3 个测试点满足,输入单词长度范围 [ 1 , 10 ] [1,10] [1,10]

所有测试点满足,输入单词长度范围 [ 1 , 1000 ] [1,1000] [1,1000]

【输入样例1】
ApPLe
【输出样例1】
ApPLe
【输入样例2】
konjac
【输出样例2】
Konjac
【原题链接】

https://www.acwing.com/problem/content/4809/


【题目分析】

签到题,使用 toupper() 函数 或者 s[i] - 32 的形式都可以。

【周赛现场 AC 代码】
#include<bits/stdc++.h>

using namespace std;

string s;

int main() {

    cin >> s;
    // 转换首字母
    if (s[0] >= 'a' && s[0] <= 'z') s[0] = s[0] - 32;
//    s[0] = toupper(s[0]);
    cout << s << endl;

    return 0;
}


【题目B】找数字

【题目描述】

给定一个正整数 m m m 和一个非负整数 s s s

请你找到长度为 m m m 且各位数字之和为 s s s 的最小和最大 非负 整数。

要求所求非负整数不得包含前导零。

【输入】

共一行,两个整数 m , s m,s m,s

【输出】

在一行内输出满足条件的最小和最大 非负 整数。

如果无解,则输出 -1 -1

【数据范围】

6 6 6 个测试点满足 1 ≤ m ≤ 3 1 \le m \le 3 1m3

所有测试点满足 1 ≤ m ≤ 100 1 \le m \le 100 1m100 0 ≤ s ≤ 900 0 \le s \le 900 0s900

【输入样例1】
2 15
【输出样例1】
69 96
【输入样例2】
3 0
【输出样例2】
-1 -1
【原题链接】

https://www.acwing.com/problem/content/4810/


【题目分析】

贪心

该题的贪心策略还是比较好想的,但是从 「贪心策略 → 具体代码」 的实现其实还是比较难的,需要考虑一些边界条件(如果是第一次做这种类型的题目的话)

【复盘后的优化代码】✅
#include<bits/stdc++.h>

using namespace std;

int main() {
    ios::sync_with_stdio(false);  //cin读入优化
    cin.tie(0);

    int sum, m;
    cin >> m >> sum;

    // m位数之和最大为 9 * m,若sum>9*m,无解
    // sum为0时,m位数大于1,也无解
    if (sum > 9 * m || sum == 0 && m > 1) cout << "-1 -1" << endl;
    else {
        // 创意长度为m,内容均为' '的字符串,下标从0开始
        string a(m, ' '), b(m, ' ');

        // 求最大值(高位尽可能放大的数)
        int tmp = sum;
        for (int i = 0; i < m; i++) {
            int t = min(9, tmp);
            a[i] = t + '0';
            tmp = tmp - t;
        }

        // 求最小值(第一位起码最少是1)
        // 从最后一位开始,从大数开始填
        for (int i = m - 1; i > 0; i--) {
            int t = min(9, sum - 1);  // 可以以样例为例子来理解
            b[i] = t + '0';
            sum = sum - t;
        }
        b[0] = sum + '0';

        // 输出结果
        cout << b << " " << a << endl;
    }

    return 0;
}
【周赛现场 AC 代码】

周赛现场未 AC



【题目C】构造字符串

【题目描述】

给定一个长度为 n n n 的由小写字母构成的字符串 t t t 以及一个整数 k k k

请你构造一个字符串 s s s,要求:

  1. 字符串 s s s 恰好有 k k k 个子串等于字符串 t t t
  2. 字符串 s s s 的长度尽可能短。

保证一定存在唯一解。

【输入】

第一行包含两个整数 n , k n,k n,k

第二行包含一个长度为 n n n 的由小写字母构成的字符串 t t t

【输出】

输出满足条件的字符串 s s s

保证一定存在唯一解。

【数据范围】

3 3 3 个测试点满足 1 ≤ n , k ≤ 4 1 \le n,k \le 4 1n,k4

所有测试点满足 1 ≤ n , k ≤ 50 1 \le n,k \le 50 1n,k50

【输入样例1】
3 4
aba
【输出样例1】
ababababa
【输入样例2】
3 2
cat
【输出样例2】
catcat
【原题链接】

https://www.acwing.com/problem/content/4811/


【题目分析】

贪心 + 前后缀

贪心策略比较容易想到,最差的情况就是把 k 个原字符串排列在一起,能够优化的情况就是相邻的两个原字符串存在重叠部分(即前缀和后缀是相同的)

前后缀 概念的举例说明:字符串 abcXXXXabc 的前缀 abc 和后缀 abc 是相等,长度为3。

因此,需要找到长度最大的,相同的前缀和后缀,然后再进行输出即可。

判断 前后缀相同 的方法:

  • KMP 算法
  • str.substr(0,cnt) == str,substr(cnt-n,cnt) 判断式枚举

🍉 PS 1:前后缀长度不能为 str.length,即原字符串长度。否则会出现 「前缀 = 后缀 = 本身」的情况,这个对于任何字符串都是成立的。

🍉 PS 2:「前后缀相等」的判断不能和 「前后缀对称」 的判断搞混。比如,字符串 abcXXXcba 应该是前缀和后缀对称的情况,而非相等的情况。前者是用 KMP 算法 或者 str.substr(pos,length) 来对比,后者是用 str[i] == str[n - i + 1] 的方式来判断。

🍉 PS 3:该题在枚举长度时,不能用「二分」来做,因为不满足「二段性」。如字符串 abcXXXabc,此时长度为 3,前缀后缀均为 abc,满足条件,因此需要继续找长度更大的;但是在长度为 2 时,前缀 ab 和 后缀 bc 不同,此时既要往长度大的找(如上面长度为 3 时就满足),又要往长度小的找(因为长度更大的可能全部不满足),所以不具有二段性。

【复盘后的优化代码】✅
  1. substr(pos,len) 前后缀暴力枚举对比
#include<bits/stdc++.h>

using namespace std;

int n, k;
string str;

int main() {
    cin >> n >> k;
    cin >> str;

    // 统计前后缀的长度
    int cnt = 0;
    for (int i = 1; i <= n - 1; i++) { // 前后缀长度不能为n,不然前缀=后缀=本身
        if (str.substr(0, i) == str.substr(n - i, i))
            cnt = i;
    }

//     输出结果
//     先输出第一个字符串
    for (int i = 0; i < n; i++) cout << str[i];
    // 在后面拼接k-1个字符串
    for (int i = 1; i <= k - 1; i++) {
        for (int j = cnt; j < n; j++) {
            cout << str[j];
        }
    }
    // 上面的输出代码可以优化
//    cout << str;  // 先输出1个完整的str
    // 再输出k-1个子串,str.substr(cnt) 表示输出从下标cnt开始的到结尾的子串
//    for (int i = 1; i <= k - 1; i++) cout << str.substr(cnt);

    return 0;
}

在数据范围更加大的题目中,使用 KMP 算法是更好的选择。

  1. KMP 算法计算前后缀
#include<bits/stdc++.h>

using namespace std;
const int N = 100;
int n, k, ne[N];
string p;

int main() {
    cin >> n >> k >> p;

    // 构建next数组
    ne[0] = 0;
    for (int i = 1, j = 0; i < p.size(); i++) {
        while (p[i] != p[j] && j) j = ne[j - 1];
        if (p[i] == p[j]) j++;
        ne[i] = j;
    }

    // 最长前后缀长度
    int max_length = ne[p.size() - 1];
    cout << p;
    for (int i = 1; i <= k - 1; i++)
        cout << p.substr(max_length);

    return 0;
}
  1. 字符串哈希
#include<bits/stdc++.h>

using namespace std;
typedef unsigned long long ULL;
const int N = 100, P = 131;

ULL h[N], p[N];
int n, k;
string str;

ULL get_hash(int l, int r) {
    return h[r] - h[l - 1] * p[r - l + 1];
}

int main() {
    cin >> n >> k >> str;

    p[0] = 1;
    for (int i = 1; i <= n; i++) {
        h[i] = h[i - 1] * P + str[i - 1];
        p[i] = p[i - 1] * P;
    }

    // 枚举前后缀长度,利用字符串哈希对比
    int res = 0;
    for (int i = 1; i <= n - 1; i++) {
        if (get_hash(1, i) == get_hash(n - i + 1, n)) {
            res = i;
        }
    }

    cout << str;
    for (int i = 1; i <= k - 1; i++) {
        cout << str.substr(res);
    }
    cout << endl;

    return 0;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值