2020年第十一届蓝桥杯国赛 B组个人题解 Jathon

试题下载

试题 A: 美丽的 2

本题总分:5 分
【问题描述】
小蓝特别喜欢 \(2\),今年是公元 \(2020\) 年,他特别高兴。
他很好奇,在公元 \(1\) 年到公元 \(2020\) 年(包含)中,有多少个年份的数位中 包含数字 \(2?\)
【我的题解】
数位分离,水题了。循环 \(1\)\(2020\),写个函数判断一下就好了

#include <cstdio>
#include <cstring>
#include <algorithm>
#include <map>
#include <set>
#include <queue>

using namespace std;
typedef long long LL;
typedef pair<int, int> PII;

bool hasDigitTwo(int number) {
    while (number) {
        if (number % 10 == 2) {
            return true;
        }
        number /= 10;
    }
    return false;
}

int main() {
    int answer = 0;
    for (int i = 1; i <= 2020; i++) {
        if (hasDigitTwo(i)) {
            answer++;
        }
    }
    printf("%d\n", answer);
    return 0;
}

我的答案 \(563\)

试题 B: 扩散

本题总分:5 分
【问题描述】
小蓝在一张无限大的特殊画布上作画。
这张画布可以看成一个方格图,每个格子可以用一个二维的整数坐标表示。
小蓝在画布上首先点了一下几个点:(0,0), (2020,11), (11,14), (2000,2000)。 只有这几个格子上有黑色,其它位置都是白色的。
每过一分钟,黑色就会扩散一点。具体的,如果一个格子里面是黑色,它 就会扩散到上、下、左、右四个相邻的格子中,使得这四个格子也变成黑色 (如果原来就是黑色,则还是黑色)。
请问,经过 \(2020\) 分钟后,画布上有多少个格子是黑色的。
【我的题解】
一开始写了个按层遍历的 \(bfs\) , 发现运行不出结果,然后换了思路。将题目换种问法,求无限大的画布里与给出的四个点中至少一个点曼哈顿距离小于等于 \(2020\) 的点有多少个。那么就可以想到, \(x\) 坐标小于 \(-3000\) 或者大于 \(5000\) 的点不可能满足这个条件,同理 \(y\) 坐标小于 \(-3000\) 或者大于 \(5000\) 的点也不可能满足这个条件。那么无限大的画布就有范围了,枚举这个范围内的点,统计有多少个满足条件的就好了。

#include <cstdio>
#include <cstring>
#include <cmath>
#include <algorithm>
#include <map>
#include <set>
#include <queue>

using namespace std;
typedef long long LL;
typedef pair<int, int> PII;

const PII POINTS[] = {{0, 0}, {2020, 11}, {11, 14}, {2000, 2000}};

bool isBlack(int x, int y, int i) {
    int disX = x - POINTS[i].first;
    int disY = y - POINTS[i].second;
    return abs(disX) + abs(disY) <= 2020;
}

int main() {
    int answer = 0;
    for (int i = -3000; i <= 5000; i++) {
        for (int j = -3000; j <= 5000; j++) {
            for (int k = 0; k < 4; k++) {
                if (isBlack(i, j, k)) {
                    answer++;
                    break;
                }
            }
        }
    }
    printf("%d\n", answer);
    return 0;
}

试题 C: 阶乘约数

本题总分:10 分
【问题描述】
定义阶乘 \(n! = 1 \times 2 \times 3 \times ··· \times n\)
请问 \(100!\)\(100\) 的阶乘)有多少个约数。
【我的题解】
对于一个大于 \(1\) 的正整数 \(n\) 可以分解质因数 \(n = \prod_{i = 1} ^ {k} p_{i} ^ {a_{i}} = p_{1} ^ {a_{1}} . p_{2} ^ {a_{2}} ...... p_{k} ^ {a_{k}}\)
\(n\) 的正约数个数就是 \(f(n) = \prod_{i = 1} ^ {k} (a_{i} + 1) = (a_{1} + 1) (a_{2} + 1) ...... (a_{k} + 1)\)

#include <cstdio>
#include <cstring>
#include <cmath>
#include <algorithm>
#include <map>
#include <set>
#include <queue>

using namespace std;
typedef long long LL;
typedef pair<int, int> PII;

vector<int> primes;
vector<int> counter;

bool isPrime(int n) {
    for (int i = 2; i * i <= n; i++) {
        if (n % i == 0) {
            return false;
        }
    }
    return true;
}

void helper(int number) {
    int pos = 0;
    while (primes[pos] <= number) {
        while (number % primes[pos] == 0) {
            number /= primes[pos];
            counter[pos]++;
        }
        pos += 1;
    }
}

int main() {
    for (int i = 2; i <= 200; i++) {
        if (isPrime(i)) {
            primes.push_back(i);
            counter.push_back(0);
        }
    }

    for (int i = 1; i <= 100; i++) {
        helper(i);
    }

    LL answer = 1;
    for (int i = 0; i < counter.size(); i++) {
        answer *= (counter[i] + 1);
    }
    printf("%lld\n", answer);
    return 0;
}

我的答案 \(39001250856960000\)
【吐槽一下】
比赛的时候累乘 \(counter[i]\) 忘了 +1, 第一个循环只循环到了 \(100\),然后这10分就白给了。更离谱的是一样的代码带回来在自己的CodeBlock上运行崩溃,但是在学校比赛的时候Dev居然能跑出一个奇怪的数字。

试题 D: 本质上升序列

本题总分:10 分
【问题描述】
小蓝特别喜欢单调递增的事物。
在一个字符串中,如果取出若干个字符,将这些字符按照在字符串中的顺 序排列后是单调递增的,则成为这个字符串中的一个单调递增子序列。
例如,在字符串 \(lanqiao\) 中,如果取出字符 \(n\)\(q\),则 \(nq\) 组成一个单 调递增子序列。类似的单调递增子序列还有 \(lnq、i、ano\) 等等。
小蓝发现,有些子序列虽然位置不同,但是字符序列是一样的,例如取第 二个字符和最后一个字符可以取到 \(ao\),取最后两个字符也可以取到 \(ao\)。小蓝 认为他们并没有本质不同。
对于一个字符串,小蓝想知道,本质不同的递增子序列有多少个?
例如,对于字符串 \(lanqiao\),本质不同的递增子序列有 \(21\) 个。它们分别 是 \(l\)\(a\)\(n\)\(q\)\(i\)\(o\)\(ln\)\(an\)\(lq\)\(aq\)\(nq\)\(ai\)\(lo\)\(ao\)\(no\)\(io\)\(lnq\)\(anq\)\(lno\)\(ano\)\(aio\)
请问对于以下字符串(共 \(200\) 个小写英文字母,分四行显示):(如果你把 以下文字复制到文本文件中,请务必检查复制的内容是否与文档中的一致。在 试题目录下有一个文件 inc.txt,内容与下面的文本相同)
\(tocyjkdzcieoiodfpbgcncsrjbhmugdnojjddhllnofawllbhf\) \(iadgdcdjstemphmnjihecoapdjjrprrqnhgccevdarufmliqij\) \(gihhfgdcmxvicfauachlifhafpdccfseflcdgjncadfclvfmad\) \(vrnaaahahndsikzssoywakgnfjjaihtniptwoulxbaeqkqhfwl\)
本质不同的递增子序列有多少个?
【我的题解】
动态规划,\(dp[i]\) 表示前 \(i\) 个字母的本质不同递增子序列个数
\(dp[i] = \sum_{j = last[s[i]] + 1} ^ {i - 1} (s[j] \lt s[i]) \times dp[j]\)
\(last[s[i]]\) 表示 \(s[i]\) 这个字母上一次出现的位置

#include <cstdio>
#include <cstring>
#include <cmath>
#include <algorithm>
#include <map>
#include <set>
#include <queue>

using namespace std;
typedef long long LL;
typedef pair<int, int> PII;

int last[30];
LL dp[210];
char s[210];

int main() {
    // freopen("inc.txt", "r", stdin);
    scanf("%s", s + 1);

    LL answer = 0;
    memset(last, -1, sizeof(last));

    dp[0] = 1;
    for (int i = 1; s[i]; i++) {
        int val = s[i] - 'a';
        for (int j = last[val] + 1; j < i; j++) {
            if (s[j] < s[i]) {
                dp[i] += dp[j];
            }
        }
        last[val] = i;
        answer += dp[i];
    }
    printf("%lld\n", answer);
    
    return 0;
}

我的答案 \(552\)

试题 E: 玩具蛇

本题总分:15 分
【问题描述】
小蓝有一条玩具蛇,一共有 \(16\) 节,上面标着数字 \(1\)\(16\)。每一节都是一 个正方形的形状。相邻的两节可以成直线或者成 \(90\) 度角。
小蓝还有一个 \(4 \times 4\) 的方格盒子,用于存放玩具蛇,盒子的方格上依次标着 字母 \(A\)\(P\)\(16\) 个字母。
小蓝可以折叠自己的玩具蛇放到盒子里面。他发现,有很多种方案可以将 玩具蛇放进去。
请帮小蓝计算一下,总共有多少种不同的方案。如果两个方案中,存在玩 具蛇的某一节放在了盒子的不同格子里,则认为是不同的方案。
【我的题解】
一个 \(dfs\) 就完事儿啦,作为E题倒是比想象中简单,不过我好像写复杂了,不需要压缩成一维。

#include <cstdio>
#include <cstring>
#include <cmath>
#include <algorithm>
#include <map>
#include <set>
#include <queue>

using namespace std;
typedef long long LL;
typedef pair<int, int> PII;

const int DIRCTIONX[] = {1, -1, 0, 0};
const int DIRCTIONY[] = {0, 0, 1, -1};

int answer;
bool visited[16];
int length;

void dfs(int n) {
    if (length == 16) {
        answer++;
        return;
    }
    for (int i = 0; i < 4; i++) {
        int x = n / 4 + DIRCTIONX[i];
        int y = n % 4 + DIRCTIONY[i];
        if (x < 0 || x >= 4 || y < 0 || y >= 4) {
            continue;
        }
        int nextN = 4 * x + y;
        if (!visited[nextN]) {
            visited[nextN] = true;
            length++;
            dfs(nextN);
            visited[nextN] = false;
            length--;
        }
    }
}

int main() {
    for (int i = 0; i < 16; i++) {
        visited[i] = true;
        length++;
        dfs(i);
        visited[i] = false;
        length--;
    }
    printf("%d\n", answer);
    return 0;
}

试题 F: 皮亚诺曲线距离

时间限制: 1.0s  内存限制: 256.0MB  本题总分:15 分
【问题描述】
这里就略了,下载链接已给出,可以自行下载查看
【输入格式】
输入的第一行包含一个正整数 \(k\),皮亚诺曲线的阶数。 第二行包含两个整数 \(x1, y1\),表示第一个点的坐标。
第三行包含两个整数 \(x2, y2\),表示第二个点的坐标。
【输出格式】
输出一个整数,表示给定的两个点之间的距离。
【评测用例规模与约定】
对于 \(30\%\) 的评测用例,\(0 \le k \le 10\)
对于 \(50\%\) 的评测用例,\(0 \le k \le 20\)
对于所有评测用例,\(0 \le k \le 100, 0 \le x1,y1,x2,y2 \le 3k, x1,y1,x2,y2 \le 10 ^ {18}\)。 数据保证答案不超过 \(10 ^ {18}\)
【我的题解】
并不会做,口胡一波吧,递归是肯定要的,然后想到的就是以 \((0, 0)\) 为基准,求出 \((x_{1}, y_{1})\) 到基准的距离,求出 \((x_{2}, y_{2})\) 到基准的距离,然后减一下。这个递归感觉有点复杂呀,作为编程大题第一题有点搞心态。

试题 G: 游园安排

时间限制: 1.0s  内存限制: 256.0MB  本题总分:20 分
【问题描述】
\(L\) 星球游乐园非常有趣,吸引着各个星球的游客前来游玩。小蓝是 \(L\) 星球 游乐园的管理员。
为了更好的管理游乐园,游乐园要求所有的游客提前预约,小蓝能看到系 统上所有预约游客的名字。每个游客的名字由一个大写英文字母开始,后面跟 \(0\) 个或多个小写英文字母。游客可能重名。
小蓝特别喜欢递增的事物。今天,他决定在所有预约的游客中,选择一部 分游客在上午游玩,其他的游客都在下午游玩,在上午游玩的游客要求按照预 约的顺序排列后,名字是单调递增的,即排在前面的名字严格小于排在后面的 名字。 一个名字 \(A\) 小于另一个名字 \(B\) 是指:存在一个整数 \(i\),使得 \(A\) 的前 \(i\) 个字 母与 \(B\) 的前 i 个字母相同,且 \(A\) 的第 \(i+1\) 个字母小于 \(B\) 的第 \(i+1\) 个字母。(如 果 \(A\) 不存在第 \(i+1\) 个字母且 \(B\) 存在第 \(i+1\) 个字母,也视为 \(A\) 的第 \(i+1\) 个字 母小于 \(B\) 的第 \(i+1\) 个字母) 作为小蓝的助手,你要按照小蓝的想法安排游客,同时你又希望上午有尽 量多的游客游玩,请告诉小蓝让哪些游客上午游玩。如果方案有多种,请输出 上午游玩的第一个游客名字最小的方案。如果此时还有多种方案,请输出第一 个游客名字最小的前提下第二个游客名字最小的方案。如果仍然有多种,依此 类推选择第三个、第四个……游客名字最小的方案。
【输入格式】
输入包含一个字符串,按预约的顺序给出所有游客的名字,相邻的游客名 字之间没有字符分隔。
【输出格式】
按预约顺序输出上午游玩的游客名单,中间不加任何分隔字符。
【评测用例规模与约定】
对于 \(20\%\) 的评测数据,输入的总长度不超过 \(20\) 个字母。
对于 \(50\%\) 的评测用例,输入的总长度不超过 \(300\) 个字母。
对于 \(70\%\) 的评测用例,输入的总长度不超过 \(10000\) 个字母。
对于所有评测用例,每个名字的长度不超过 \(10\) 个字母,输入的总长度不超 过 \(1000000\) 个字母。
【我的题解】
将每个名字哈希掉,为了确保按字典序,所有名字的长度都统一成 \(10\),不足 \(10\) 的就后面用 \(0\) 填位,\(26\) 个字母加上填位用的 \(0\)\(BASE\) 就设成 \(27\) ,之后就是找最大上升子序列的经典问题了。刚好前几题和同事回顾了一下最大上升子序列。

#include <cstdio>
#include <cstring>
#include <cmath>
#include <algorithm>
#include <map>
#include <set>
#include <queue>

using namespace std;
typedef long long LL;
typedef pair<int, int> PII;

const int MAXN = 1000010;
const LL INF = 0x3f3f3f3f3f3f3f3f;
const int BASE = 27;

vector<LL> hashCode;
map<LL, LL> pre;
vector<LL> dp;
char s[MAXN];
LL stack[MAXN];
int str[20];

void hashToString(LL code) {
    for (int i = 1; i <= 10; i++) {
        str[i] = code % BASE;
        code /= BASE;
    }
    putchar(str[10] + 'A' - 1);
    for (int i = 9; i >= 1; i--) {
        if (str[i] == 0) {
            break;
        }
        putchar(str[i] + 'a' - 1);
    }
}

int main() {
    int length = 0, pos = 0;
    scanf("%s", s);
    while (s[pos] != '\0') {
        LL code = s[pos++] - 'A' + 1;
        for (int j = 2; j <= 10; j++) {
            if ((s[pos] == '\0') || (s[pos] >= 'A' && s[pos] <= 'Z')) {
                code *= BASE;
                continue;
            } else {
                code = BASE* code + (s[pos++] - 'a' + 1);
            }
        }
        hashCode.push_back(code);
        dp.push_back(INF);
        length++;
    }

    dp[0] = 0;
    dp.push_back(INF);

    LL longestLength = -1;
    LL largestCode = -1;
    for (int i = 0; i < length; i++) {
        int id = lower_bound(dp.begin(), dp.end(), hashCode[i]) - dp.begin();
        pre[hashCode[i]] = dp[id - 1];
        dp[id] = hashCode[i];
        if (id > longestLength) {
            longestLength = id;
            largestCode = hashCode[i];
        }
    }

    for (int i = 1; i <= longestLength; i++) {
        stack[i] = largestCode;
        largestCode = pre[largestCode];
    }

    for (int i = longestLength; i >= 1; i--) {
        hashToString(stack[i]);
    }
    puts("");
    return 0;
}

试题 H: 答疑

时间限制: 1.0s  内存限制: 256.0MB  本题总分:20 分
【问题描述】
\(n\) 位同学同时找老师答疑。每位同学都预先估计了自己答疑的时间。 老师可以安排答疑的顺序,同学们要依次进入老师办公室答疑。
一位同学答疑的过程如下: \(1.\) 首先进入办公室,编号为 \(i\) 的同学需要 \(s_{i}\) 毫秒的时间。 \(2.\) 然后同学问问题老师解答,编号为 \(i\) 的同学需要 \(a_{i}\) 毫秒的时间。 \(3.\) 答疑完成后,同学很高兴,会在课程群里面发一条消息,需要的时间可 以忽略。 \(4.\) 最后同学收拾东西离开办公室,需要 \(e_{i}\) 毫秒的时间。一般需要 \(10\) 秒、 \(20\) 秒或 \(30\) 秒,即 \(e_{i}\) 取值为 \(10000\)\(20000\)\(30000\)。 一位同学离开办公室后,紧接着下一位同学就可以进入办公室了。 答疑从 \(0\) 时刻开始。老师想合理的安排答疑的顺序,使得同学们在课程群 里面发消息的时刻之和最小。
【输入格式】
输入第一行包含一个整数 \(n\),表示同学的数量。 接下来 \(n\) 行,描述每位同学的时间。其中第 \(i\) 行包含三个整数 \(s_{i}\), \(a_{i}\), \(e_{i}\),意 义如上所述。
【输出格式】
输出一个整数,表示同学们在课程群里面发消息的时刻之和最小是多少。
【评测用例规模与约定】
对于 \(30\%\) 的评测数据,\(1 \le n \le 20\)
对于 \(60\%\) 的评测用例,\(1 \le n \le 200\)
对于所有评测用例,\(1 \le n \le 1000,1 \le s_{i} \le 60000,1 \le a_{i} \le 1000000, e_{i} \in {10000,20000,30000}\),即 \(e_{i}\) 一定是 \(10000、20000、30000\) 之一。
【我的题解】
假如我在第 \(i\) 次解答了 \(j\) 同学的问题,那么 \(j\) 之后的同学都要等 \(s_{i} + a_{i} + e_{i}\) 毫秒。同时,第 \(j\) 个同学需要等待\(s_{i} + a_{i}\)。 也就是花费 \((n - i).(s_{i} + a_{i} + e_{i}) + (s_{i} + a_{i})\)。循环 \(n\) 次,每次贪心的找最小花费累加就好了。

#include <cstdio>
#include <cstring>
#include <cmath>
#include <algorithm>
#include <string>
#include <map>
#include <set>
#include <queue>

using namespace std;
typedef long long LL;
typedef pair<int, int> PII;

const int MAXN = 1010;
const LL INF = 0x3f3f3f3f3f3f3f3f;

LL a[MAXN], b[MAXN], c[MAXN];
bool visited[MAXN];

int main() {
    int n;
    
    scanf("%d", &n); 
    for (int i = 1; i <= n; i++) {
        scanf("%lld%lld%lld", &a[i], &b[i], &c[i]);
    }
    
    LL answer = 0;
    for (int i = 1; i <= n; i++) {
        LL minCost = INF;
        LL minPos = -1;
        for (LL j = 1; j <= n; j++) {
            if (visited[j]) {
                continue;
            }
            LL cost = a[j] + b[j];
            cost += (n - i) * (a[j] + b[j] + c[j]);
            if (cost < minCost) {
                minCost = cost;
                minPos = j;
            }
        }
        answer += minCost;
        visited[minPos] = true;
    }
    printf("%lld\n", answer);
    return 0;
}

试题 I: 出租车

时间限制: 1.0s  内存限制: 256.0MB  本题总分:25 分
【我的题解】
说实话,连题目都没看,先挖个坑,待填,也不知道什么时候会填上。

试题 J: 质数行者

时间限制: 1.0s  内存限制: 256.0MB  本题总分:25 分
【问题描述】
小蓝在玩一个叫质数行者的游戏。 游戏在一个 \(n \times m \times w\) 的立体方格图上进行,从北到南依次标号为第 \(1\) 行到 第 \(n\) 行,从西到东依次标号为第 \(1\) 列到第 \(m\) 列,从下到上依次标号为第 \(1\) 层到 第 \(w\) 层。 小蓝要控制自己的角色从第 \(1\) 行第 \(1\) 列第 \(1\) 层移动到第 \(n\) 行第 \(m\) 列第 \(w\) 层。每一步,他可以向东走质数格、向南走质数格或者向上走质数格。每走到 一个位置,小蓝的角色要稍作停留。 在游戏中有两个陷阱,分别为第 \(r_{1}\) 行第 \(c_{1}\) 列第 \(h_{1}\) 层和第 \(r_{2}\) 行第 \(c_{2}\) 列第 \(h_{2}\) 层。这两个陷阱的位置可以跨过,但不能停留。也就是说,小蓝不能控制角 色某一步正好走到陷阱上,但是某一步中间跨过了陷阱是允许的。
小蓝最近比较清闲,因此他想用不同的走法来完成这个游戏。所谓两个走 法不同,是指小蓝稍作停留的位置集合不同。
请帮小蓝计算一下,他总共有多少种不同的走法。
提示:请注意内存限制,如果你的程序运行时超过内存限制将不得分。
【输入格式】
输入第一行包含两个整数 \(n, m, w\),表示方格图的大小。 第二行包含 6 个整数,\(r_{1}, c_{1}, h_{1}, r_{2}, c_{2}, h_{2}\),表示陷阱的位置。
【输出格式】
输出一行,包含一个整数,表示走法的数量。答案可能非常大,请输出答 案除以 \(1000000007\) 的余数。
【评测用例规模与约定】
对于 \(30\%\) 的评测用例 \(1 \le n,m,w \le 50\)
对于 \(60\%\) 的评测用例 \(1 \le n,m,w \le 300\)
对于所有评测用例,\(1 \le n,m,w \le 1000,1 \le r1,r2 \le n, 1 \le c1,c2 \le m, 1 \le h1,h2 \le w\),陷阱不在起点或终点,两个陷阱不同。

【我的题解】
\(O(n ^ 3)\) 去动态规划,预计 \(30\%\)\(60\%\)

#include <cstdio>
#include <cstring>
#include <cmath>
#include <algorithm>
#include <map>
#include <set>
#include <queue>

using namespace std;
typedef long long LL;
typedef pair<int, int> PII;

const int MAXN = 310;
const int MOD = 1e9 + 7;

LL dp[MAXN][MAXN][MAXN];
int primes[] = {2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47};

int main() {
    int n, m, w;
    int r1, c1, h1, r2, c2, h2;
    scanf("%d%d%d", &n, &m, &w);
    scanf("%d%d%d%d%d%d", &r1, &c1, &h1, &r2, &c2, &h2);
    for (int i = 1; i <= n; i++) {
        for (int j = 1; j <= m; j++) {
            for (int k = 1; k <= w; k++) {
                if (i == 1 && j == 1 && k == 1) {
                    dp[i][j][k] = 1;
                }
                if (i == r1 && j == c1 && k == h1) {
                    continue;
                }
                if (i == r2 && j == c2 && k == h2) {
                    continue;
                }
                for (int p = 0; p < 15; p++) {
                    if (i - primes[p] >= 1) {
                        dp[i][j][k] = (dp[i][j][k] + dp[i - primes[p]][j][k]) % MOD;
                    }
                    if (j - primes[p] >= 1) {
                        dp[i][j][k] = (dp[i][j][k] + dp[i][j - primes[p]][k]) % MOD;
                    }
                    if (k - primes[p] >= 1) {
                        dp[i][j][k] = (dp[i][j][k] + dp[i][j][k - primes[p]]) % MOD;
                    }
                }
            }
        }
    }
    printf("%lld\n", dp[n][m][w]);
    return 0;
}

总结

国二退役啦

原文地址:https://www.cnblogs.com/Jathon-cnblogs/p/13974903.html