PAT 520 钻石争霸赛 —— 全题解

本次contest的全部试题使用了C++进行求解!其中

using LL = long long 可以看作 #define LL long long

C++语言学习强迫症 之 取消了using namespace std的写法(虽然比赛时还是写了,原因见C++命名空间冲突)

初始化使用了C++11的列表初始化 -> A{ 0 },可以避免编译器的自动窄化

显示类型转换使用了C++的static_cast<>效果等同于()转换

有人说你这也不C++啊,scanf、printf用的这么溜🤔(原因:scanf、printf 的IO速度比 cin、cout快,某些情况下卡的就是你的IO时间,保留小数位啥的也是printf好用些)

本次contest使用了STL中的string、vector、upper/lower_bound~具体资料可去STL快速入门进行查看学习!

考试周 (5分)

思路

签到题手速题,没啥好说的。按要求输出即可。

代码

#include <cstdio>

int main() {

    int A{ 0 }, B{ 0 };

    scanf("%d %d", &A, &B);

    printf("%d/%.1lf=%d\n", A, (A * 1.0 / B), B);

    return 0;
}

真的恭喜你 (10分)

思路

手速题*2,if-else即可。

代码

#include <cstdio>

int main() {

    int marks{ 0 };

    scanf("%d", &marks);

    if (marks >= 90) {
        printf("gong xi ni kao le %d fen!\n", marks);
    } else {
        printf("kao le %d fen bie xie qi!\n", marks);
    }

    return 0;
}

平均成绩 (10分)

思路

这道题相对前两题稍微复杂些,给人的感觉——前两题应该是5分,这题15分。

但这题也还是非常基础的统计模拟题了~同样的按要求进行计算和输出。

我们可以定义一个sex变量来控制分别往男生/女生的统计变量里加,具体看代码~

代码

#include <cstdio>

int main() {

    int nums{ 0 }, male{ 0 }, female{ 0 }, maleCnt{ 0 }, femaleCnt{ 0 };
    int sex{ 0 }, mark{ 0 };

    scanf("%d", &nums);

    for (int i = 0; i < nums; i++) {
        scanf("%d %d", &sex, &mark);
        // sex为 1 时加到男生统计变量里去
        if (sex) {
            male += mark;
            maleCnt++;
        } else {
            female += mark;
            femaleCnt++;
        }
    }

    int sumMark = male + female;
    int sumCnt = maleCnt + femaleCnt;

    if (sumCnt == 0) {
        putchar('X');
    } else {
        printf("%.1lf", sumMark * 1.0 / sumCnt);
    }

    if (maleCnt == 0) {
        printf(" X");
    } else {
        printf(" %.1lf", male * 1.0 / maleCnt);
    }

    if (femaleCnt == 0) {
        puts(" X");
    } else {
        printf(" %.1lf\n", female * 1.0 / femaleCnt);
    }

    return 0;
}

古风A+B (15分)

思路

使用string(STL)瞬间变成5分题~

具体步骤:使用std::to_string函数将A + B的和转为string,一个字符输出一行即可。

其实很多题都是看数据范围的~ 如果A + B超过了long long那就是20分+的题了~

数据范围很小,能暴力就暴力吧😏

提供C语言思路,相加后存到字符数组里(取位操作大家都会吧 -> while循环对10取余然后除以10,条件是n > 0)然后再输出,负数的话就记个标志位, 先输出负号

代码

#include <cstdio>
#include <string>

int main() {

   int a{ 0 }, b{ 0 };

   scanf("%d %d", &a, &b);

   std::string str = std::to_string(a + b);

   // for-each循环,等同于 for (int i = 0; i < str.length(); i++) printf("%c\n", str[i]);
   for (char c : str) {
       printf("%c\n", c);
   }

   return 0;
}

猜近似数字 (15分)

思路

模拟题 + 1, 同样的思路, 使用C++比C要方便得多, 三个步骤:

  • 首先将两个字符串的长度进行比较, 长度不相等直接输出"No"
  • 接下来比较两个字符串相同位置字符不相同的次数, 大于一次直接输出"No"
  • 最后剩下的情况就是一位不同, 将它们相减取绝对值, 如果大于1, 输出"No"

最后剩下的就是符合要求的字符串, 输出"Yes"

代码

#include <iostream>
#include <string>

using std::cin;
using std::cout;
using std::endl;
using std::string;
using std::getline;

bool judge(string& a, string& b) {
    int len{static_cast<int>(a.size())};
    int cnt{ 0 };

    for (int i = 0; i < len; i++) {
        if (a[i] != b[i]) {
            cnt++;
            if (cnt > 1 || (abs((a[i] - '0') - (b[i] - '0'))) > 1) {
                return false;
            }
        }
    }

    return true;
}

int main() {

    string ans, guess;

    // getline用来读取一行字符串,包括空格
    getline(cin, ans);
    do {
        getline(cin, guess);
        if (guess == "-1") {
            break;
        }
        if (ans.size() != guess.size()) {
            cout << "No" << endl;
            continue;
        }
        cout << (judge(ans, guess) ? "Yes" : "No") << endl;
    } while (guess != "-1");

    return 0;
}

随机输一次 (20分)

思路

还是模拟…emm

两个循环, 一个是总循环, 一个是系统"黑幕"的次数循环, 每次循环结束要输一次, 其他的时候都是赢的,碰到"End"结束总循环, 唯一需要注意的是, 黑幕循环需要使用取模得到相应的循环下标

代码

#include <iostream>
#include <vector>

using std::cin;
using std::cout;
using std::endl;
using std::string;
using std::vector;

string win(string& p) {
    if (p == "ChuiZi") {
        return "Bu";
    } else if (p == "Bu") {
        return "JianDao";
    }
    return "ChuiZi";
}

string lose(string& p) {
    if (p == "ChuiZi") {
        return "JianDao";
    } else if (p == "Bu") {
        return "ChuiZi";
    }
    return "Bu";
}

int main() {

    int num{ 0 };
    cin >> num;
    vector<int> v(num);

    for (int i = 0; i < num; i++) {
        cin >> v[i];
    }

    getchar();
    string player;
    int temp{ 0 }, index{ 0 };

    while (true) {
        temp = v[index % num];
        index++;
        for (int i = 0; i < temp; i++) {
            getline(cin, player);
            if (player == "End") {
                return 0;
            }
            cout << win(player) << endl;
        }

        getline(cin, player);
        if (player == "End") {
            break;
        }
        cout << lose(player) << endl;
    }

    return 0;
}

阶乘的非零尾数 (20分)

思路

比赛的时候先去做最后一题了,最后仔细读题后只剩下几分钟了,于是没有完成(其实也还是不太会)

赛后重新买了一次机会, 看了大佬的题解, 才磨出来…真的, 感觉这题难度和前面的题不在一个档次(因为很令人迷惑,调了好久才发现测试点在卡哪里的代码)

坑点在代码中标注出来…还是没太get到这题要我们干嘛

这题可以分为两题的组合:

  • n的阶乘的最后k位数, k位数可以有k - 1个前导0而不能有一个尾随0 [巨坑]
  • 经典面试/数学题, n的阶乘最后有几个尾随0 (与因子5有关,实在不理解可以记住这个代码片) [不坑]

代码

#include <cstdio>

// 咱也不敢问,LL = long long时是不能AC的
using LL = __int128;
/**
	不敢问 +1, 题目给出的最大k是9, 按道理对 1e10 取余就可以了,结果它卡了最后一个测试点(1分)
	这里试过e后面的指数取10~15, 唯独13可以AC, 其他都不可
*/
const LL MOD = 1e13;

// 记住结论就行
int getZeroCnt(int num) {
    int cnt = 0;

    while (num > 0) {
        num /= 5;
        cnt += num;
    }

    return cnt;
}

/**
	输出k位数, 试过转string补零等操作, 不可AC [坑]
	照搬大佬的递归输出
*/
void printNum(LL num, int index, int k) {
    if (index == k) {
        return;
    }
    printNum(num / 10, index + 1, k);
    putchar(num % 10 + '0');
}


int main() {

    int n{ 0 }, k{ 0 };
    LL num{ 1 };

    scanf("%d %d", &n, &k);

    /**
    	0 < n < 1e7, 求n的阶乘, C++没有大数(其实也没必要, 好像特别大的数只需要关注需要
    	保留的位数即可, 但自己还是不太明白, 数学什么的好难啊!), 比如结果对 1e9 + 7 取余
    */
    for (int i = 1; i <= n; i++) {
        num *= i;
        // 去掉尾随0
        while (num % 10 == 0) {
            num /= 10;
        }
        num %= MOD;
    }

    printNum(num, 0, k);
    printf(" %d\n", getZeroCnt(n));

    return 0;
}

三足鼎立 (25分)

思路

是它! 是它! 就是它! 比赛中做的最后一题~ 暴力骗分🤔给了16分, 蛮厚道的了~

花里胡哨的描述可以简化为:给定一条边,求其他可以组成三角形的两边组合。

大家都知道,组成三角形的边数需要满足任意两边之和大于第三边,这条定义可以简化为较小两边之和大于最大边即可组成三角形。

后面去leetcode找到了三角形选边的问题,可用二分、双指针来做,可leetcode是任意选三边,这道题是固定了一边,求另外两边。在草稿纸上写写画画,想用双指针来做,但是不可,因为固定的那条边可以是最大的边,也可以是小边中的任意一条…如果头铁写的话那就相当于枚举了,头痛。。。

最终在大佬题解的帮助下,懵懵懂懂的A了这道题,具体思路为使用二分,直接调用STL的upper/lower_bound,做差统计即可,还有一些小细节代码里说。

代码

#include <cstdio>
#include <cmath>
#include <vector>
#include <algorithm>

using LL = long long;
using std::vector;
using std::upper_bound;
using std::lower_bound;

int main() {

    int num{ 0 }, a{ 0 };
    // 计数要long long,大佬说是数据范围 1e5 导致的int存储可能会爆
    LL cnt{ 0L };

    scanf("%d %d", &num, &a);
    // 要多申请一个空间,也就是下标0不做存储操作,后面二分有用
    vector<int> v(num + 1);
    for (int i = 1; i <= num; i++) {
        scanf("%d", &v[i]);
    }

    // 避开下标0的排序
    sort(v.begin() + 1, v.end());
    /**
    	A 为固定的那条边
    	right = 不满足 A + B > C 的最小下标
    	left = 满足 B - A < C 与 A - B < C 的最大下标
    	下标 0 的空位貌似可以使得这两个函数的搜索避免 B 和 C 的重复
    */
    for (LL i = 1; i <= num; i++) {
        LL left = static_cast<LL>(upper_bound(v.begin() + i + 1, v.end(), abs(a - v[i])) - v.begin() - 1);
        LL right = static_cast<LL>(lower_bound(v.begin() + i + 1, v.end(), a + v[i]) - v.begin() - 1);
        cnt += right - left;
    }

    printf("%lld\n", cnt);

    return 0;
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值