You are on page 1of 10

厦门大学2021届铃盛杯C语言积分赛决赛题解

A. 病床边的糖果
命题人:ATt3Z1 难度:难

题目背景:《病床边的数字》的后续,没有《病床边的数字》难。

赛前评价: 综合型难题,细节繁琐,代码实现较为复杂,对选手的思维能力要求较高,对代码能力
要求极高。

本题给了 2 倍的步数限制,还是比较宽松的。此处提供一个解法:维护糖果数最多的三罐,然后剩
下的糖果全部移入这三罐中,然后对这三罐糖果进行处理。

#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#define judgeok foru(i,1,n){if(sum == a[i])return;}
#define foru(i,a,b) for(int i = (a), _##i = (b); i <= _##i; ++i)
#define gg puts("0");
void cout();
bool flagno = 0;
struct { int a, b, c; }o[2000];
int sum = 0, ind = 0, n;
int a[2000] = { 0 };
void swap(int* a, int* b) { int temp; temp = *a; *a = *b; *b = temp; }
void cout() {
if (flagno == 1) return (void)printf("0\n");
printf("%d\n", ind);
foru(i, 1, ind) printf("%d %d %d\n", o[i].a, o[i].b, o[i].c);
}
void move(int a1, int b, int c) {
o[++ind].a = a1; o[ind].b = b; o[ind].c = c;
a[a1]--; a[b]--; a[c] += 2;
}
void run3() {
int fir = 0, sec = 0, thr = 0;
int maxx = 1000;
foru(i, 1, 3) {
foru(j, i + 1, 3) {
if (abs(a[i] - a[j]) % 3 == 0 && a[i] + a[j] < maxx) {
maxx = a[i] + a[j];
fir = i, sec = j;
}
}
}
if (!fir) { flagno = 1; return; }
if (a[fir] < a[sec]) swap(&fir, &sec);
thr = 6 - fir - sec;
while (a[fir] > a[sec]) {
if (a[thr] == 0) move(fir, sec, thr);
move(fir, thr, sec);
}
foru(i, 1, a[fir]) move(fir, sec, thr);
}
int fir = 0, sec = 0, thr = 0, fou = 0;
void prepare() {
int x = 1, y = 1, z = 1;
while (1) {
while (a[x] == 0 || x == y || x == fir || x == sec || x == thr)
x++;
while (a[y] == 0 || x == y || y == fir || y == sec || y == thr)
y++;
if (x > n || y > n) break;
if (a[sec] > a[thr] + 1) {
move(x, sec, fir);
move(y, sec, fir);
continue;
}
move(x, y, fir);
}
while (a[z] == 0 || z == fir || z == sec || z == thr) {
z++;
if (z > n)break;
}
if (z == 0 || z == fir || z == sec || z == thr || z > n) return;
while (a[z] > 0) {
if (a[sec] > a[thr] && a[sec] > 0) move(sec, z, fir);
else if (a[thr] > 0 && a[thr] >= a[sec]) move(thr, z, fir);
else move(fir, z, sec);
}
}
void Mysort() {
int max[5] = { -1,-1,-1,-1,-1 };
fir = 0, sec = 0, thr = 0, fou = 0;
foru(i, 1, n) {
if (a[i] > max[1]) {
for (int i = 4; i >= 2; --i) {
max[i] = max[i - 1];
}max[1] = a[i];
fou = thr, thr = sec, sec = fir, fir = i;
}
else if (a[i] > max[2]) {
max[4] = max[3]; max[3] = max[2];
max[2] = a[i];
fou = thr, thr = sec, sec = i;
}
else if (a[i] > max[3]) {
max[4] = max[3]; max[3] = a[i];
fou = thr; thr = i;
}
else if (a[i] > max[4]) {
max[4] = a[i]; fou = i;
}
}
}
void run() {
judgeok; Mysort(); prepare(); judgeok; Mysort();
while (1) {
judgeok;
Mysort();
if (a[thr] == 0 && a[fir] == 2 && a[sec] == 1) {
flagno = 1;
return;
}
if (a[sec] == a[thr] + 1 && a[fir] > 2) {
move(fir, sec, fou);
move(fir, fou, sec);
move(fir, fou, thr);
foru(i, 1, a[thr]) move(thr, sec, fir);
}
if (a[fir] == a[thr]) {
foru(i, 1, a[fir]) move(fir, thr, sec);
}
if (a[sec] == a[thr]) {
foru(i, 1, a[thr]) move(sec, thr, fir);
}
if (a[sec] == a[fir]) {
foru(i, 1, a[fir]) move(fir, sec, thr);
}
judgeok;
move(fir, sec, thr);
}
}
int solve() {
flagno = 0; ind = 0; sum = 0;
scanf("%d", &n);
foru(i, 1, n) {
scanf("%d", &a[i]);
sum += a[i];
}
foru(i, 1, n) if (sum == a[i]) return puts("0");
if (n == 3) run3();
else run();
cout();
return 0;
}
int main() {
int test;
scanf("%d", &test);
while (test--) solve();
return 0;
}

赛后评价: 本题读题难度较低,可能让一些同学误判了本题难度,所有提交的代码都没有通过第一
个测试点。

B. 与我一战,三生有幸
命题人:ATt3Z1 难度:中等

题目背景: 本题灵感来源于游戏《DOTA2》里卡尔的技能。

赛前评价: 本届决赛的模拟题,综合考察了选手的代码能力。

按题意模拟即可,注意代码的 简化 ,之前在热身赛题解里也有提醒,不会简化代码就是给自己找罪
受。

#include <stdio.h>
#include <string.h>
// 1:EEE-T, 2:QQQ-Y, 3:WWW-C, 4:EEQ-F, 5:EEW-D, 6:QQW-V, 7:QQE-G, 8:WWQ-X,
9:WWE-Z, 10:QWE-B
// 1:Q, 10:W, 100:E
char s[400000];
int len;
char ball[3];
char tempskill[2];
int ans = 0;
void pushball(char balltype) {
ball[0] = ball[1];
ball[1] = ball[2];
ball[2] = balltype;
}
void changeskill() {
char tm = tempskill[0];
tempskill[0] = tempskill[1];
int sum = 0;
for (int i = 0; i < 3; i++) {
if (ball[i] == 'Q') sum += 1;
else if (ball[i] == 'W') sum += 10;
else if (ball[i] == 'E') sum += 100;
else sum -= 20210923;
}
switch (sum) {
case 3: tempskill[1] = 'Y'; break;
case 12: tempskill[1] = 'V'; break;
case 21: tempskill[1] = 'X'; break;
case 30: tempskill[1] = 'C'; break;
case 102: tempskill[1] = 'G'; break;
case 111: tempskill[1] = 'B'; break;
case 120: tempskill[1] = 'Z'; break;
case 201: tempskill[1] = 'F'; break;
case 210: tempskill[1] = 'D'; break;
case 300: tempskill[1] = 'T'; break;
}
if (tempskill[1] == tempskill[0]) tempskill[0] = tm;
}
void useskill(char skilltype) {
if (skilltype == tempskill[1] || skilltype == tempskill[0])
ans++;
}
void run() {
ans = 0;
scanf("%s", s);
len = strlen(s);
ball[0] = ball[1] = ball[2] = 0;
tempskill[0] = tempskill[1] = 0;
for (int i = 0; i < len; i++) {
if (s[i] == 'Q' || s[i] == 'W' || s[i] == 'E') pushball(s[i]);
else if (s[i] == 'R') changeskill();
else if (s[i] >= 'A' && s[i] <= 'Z') useskill(s[i]);
}
printf("%d\n", ans);
}
int main() {
int test;
scanf("%d ", &test);
while (test--) run();
return 0;
}

赛后评价: 果然正如赛前所预料的,部分同学采用了硬写代码的策略,有些成功了,有些迷失了。
这部分同学可以参考一下标答代码。

C. 中国可可之谜
出题人:LZZ 难度:简单

本题考查C语言反义字符知识。

想要输出题目中的字符画,需要做以下处理:

1. 将字符\改为反义字符\
2. 将字符"改为反义字符
3. 处理换行问题。

因此,我们需要:将 \ 替换成 \\ ,再将 " 替换为 \" ,最后将回车全部替换为 \n 。得到一个很长


的字符串,然后打印。

具体可以使用:

1. 编辑器的查找替换功能
2. 将样例保存到文件。自己写一个C语言程序从文件中读写,实现如上所述的替换策略。
3. 手动替换(不建议,耗时太久了)

这里我们提供上述做法2中利用文件读写实现查找替换功能的标程:

#include <stdio.h>
char s[100000];
int main() {
int q = 0;
freopen("sample.out", "r", stdin);
freopen("out.txt", "w", stdout);
while ((s[q] = getchar()) != EOF) {
if (s[q] == '\\') {
s[q++] = '\\';
s[q++] = '\\';
}
else if (s[q] == '\"') {
s[q++] = '\\';
s[q++] = '\"';
}
else if (s[q] == '\'') {
s[q++] = '\\';
s[q++] = '\'';
}
else if (s[q] == '\n') {
s[q++] = '\\';
s[q++] = 'n';
}
else q++;
}
printf("%s", s);
return 0;
}
得到输出以后,将 out.txt 结果复制黏贴到puts()函数中,然后提交代码,即可通过本题。

赛后评价: 本场比赛最搞笑的题,满机房的显示屏都是唐可可的字符画。技术好的同学两分钟就通
过了此题,而很多同学采用手工一个一个替换字符的做法,花了好几个小时,这样过于低效了。

D. 回文日期
出题人:Pecco 难度:简单

注意到每年最多只有一个回文日期,所以直接枚举所有月日,看它们颠倒后是否是y1到y2之间的年
份即可。

值得注意的是,0229颠倒后是9220年,恰好是一个闰年,所以92200229是回文日期。

#include <stdio.h>
int months[] = {0, 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
int rev(int md) {
int a = md / 1000;
int b = md / 100 % 10;
int c = md / 10 % 10;
int d = md % 10;
return d * 1000 + c * 100 + b * 10 + a;
}
int ans[10000];
int main() {
int y1, y2;
scanf("%d%d", &y1, &y2);
for (int m = 1; m <= 12; ++m) {
for (int d = 1; d <= months[m]; ++d) {
int md = m * 100 + d;
int y = rev(md);
if (y >= y1 && y <= y2)
ans[y] = y * 10000 + md;
}
}
for (int i = y1; i <= y2; ++i)
if (ans[i]) {
printf("%d\n", ans[i]);
}
return 0;
}

赛后评价: 此题难度较低,大部分同学都过了。

E. 雅思分数计算
出题人:Pecco 难度:签到

实际上,如果我们考虑分数的两倍,那么这就是四舍五入。所以先算出分数的两倍,四舍五入后除
以二即可。
#include <stdio.h>
#include <math.h>
int main() {
int t;
scanf("%d", &t);
while (t--) {
double a, b, c, d;
scanf("%lf%lf%lf%lf", &a, &b, &c, &d);
printf("%.1f\n", round((a + b + c + d) / 2) / 2);
}
return 0;
}

赛后评价: 全场最简单的题目,所有人都通过的一道题。不过最开始做的人却偏少。不知道为什么
大家没有发现这题是最简单的。

F. 希腊天平之谜
出题人:LZZ 难度:难

不难看出, 和 时一定不能盈利.

当参赛选手为奇数时,本轮游戏一定不会出现平局。

假设本轮有 个人且 为 的奇数,则下一轮的获胜方,其可能的人数在 到 之间。不妨设


为n人参与的一轮游戏下能够获胜的最小人数。

因此,作为参赛方,我们只需要保证天平左右两边都有我们的队友,并且每一边的人数都至少为
即可获得胜利。

因此。当 为奇数且 时: 。

现在我们考虑偶数参赛者的情况。当 为 的偶数。我们不难发现。如果仿照奇数参赛者的策略,
则难以避免出现平局的情况。特别是对手可能专门准备了一个和你相反的策略,这样就必然导致平局。

为了防止出现无数次平局,我们需要多一个人,此人随机选择天平的左侧或右侧放置筹码。则出现
无数次平局的概率无限趋于 ,胜率达到 。

因此。当n为偶数时,

知道了这个规律,我们就只需要递推即可。得出人数以后可以很轻松的判断能否盈利。

最后,我们注意数列 的初始值。

如果想要拿下全部奖金,则只需设 ,

如果要拿下一半奖金,只需设 ,

通过两种不同的初值可以得到拿下全部奖金/至少拿下一半奖金所需的最少团队人数。
最后选择最符合题意的答案输出即可。(人均收益最高的前提下,满足团队人数尽可能少)

实际上,通过上述方法计算出结果后,我们可以发现,只有n=5时优先考虑拿一半奖金,其余情况
下,选择拿走全部奖金的策略一定更优。因此可以省略一部分代码。

#include<stdio.h>
#define maxn 100005
int dp[maxn]={0,1,2};
void pre_cal(int *dp)
{
for (int i=3,max=1;i+1<maxn;i+=2)
{
dp[i]=max*2;
dp[i+1]=max*2+1;
max=max>dp[i/2+1]?max:dp[i/2+1];
}
}
int main(void)
{
int t;
scanf("%d",&t);
pre_cal(dp);
while(t--)
{
int n,m,c,ans=0;
scanf("%d%d%d",&n,&m,&c);
if (dp[n]*m<c)
ans=(n==5)?2:dp[n];
printf("%d\n",ans);
}
return 0;
}

赛后评价:这道题主要难度在于理解题意,代码本身非常简单,其实不比G题难,可惜的是,大部
分人都没有去做这道题。有一个同学提交的代码非常接近正解,但是没有考虑求max,只取了 一个
点,这样是有反例的。

G. Nowhere Land
命题人:ATt3Z1 难度:难

题目背景:《LIES & TIES》的后续,比《LIES & TIES》难。

赛前评价:偏数学的一道题目,考察选手的数学思维。

本题数据范围极大,暴力是肯定过不去的。

本道题最关键的式子:对于任意正整数 ,均有 。因为若 为 2 ,则


;若 为奇数,则 ;若 为大于 2 的偶数,则 。

然后按 分类讨论即可。

若 ,则 一定是奇数。

若 ,则 一定是 ~ 的最小公倍数的倍数且不是 的倍数 (本题中


)。

若 ,则除去上面两种情况即可。

#include <stdio.h>
typedef long long ll;
const ll mod = 20210923;
ll L3[10], div[10];
ll gcd(ll a, ll b) { return b == 0 ? a : gcd(b, a % b); }
void prepare() {
ll pow2 = 2;
for (int i = 1; i <= 4; i++) {
pow2 <<= 1;
ll lcm = pow2 >> 1;
for (ll j = 3; j < pow2; j += 2) {
lcm = lcm * j / gcd(lcm, j);
}
L3[i] = lcm;
div[i] = L3[i] << 1;
}
}
void run() {
ll a, b, c, Lnum[4] = {0};
scanf("%lld%lld%lld", &a, &b, &c);
ll even = b / 2 - (a - 1) / 2, odd = b - a + 1 - even;
Lnum[1] = odd;
for (int i = 1; i <= 4; ++i) {
Lnum[3] += b / L3[i] - b / div[i] - (a - 1) / L3[i] + (a - 1) / div[i];
}
Lnum[2] = even - Lnum[3];
if (c <= 3) printf("%lld\n", Lnum[c] % mod);
else printf("0\n");
}
int main() {
prepare();
int test;
scanf("%d", &test);
while (test--) run();
return 0;
}

赛后评价:很多同学采取了瞎猜或者暴力的策略,显然瞎猜或暴力是过不去的。实际角度来说,打
表的做法是可以通过而且效率较高的。

H. 叠词词,恶心心III
出题人:Pecco 难度:极难

分治法。如果存在一个子串是叠词,那么它要么完全出现在左半边,要么完全出现在右半边,要么
跨过中间点。前两种可以递归处理,我们考虑第三种情况。

若一个叠词位于左半边中的字符数更多,则称它为左偏的,否则称它为右偏的。例如,在
dabcaabcaccd中,加粗表示的子串就是一个左偏的叠词。对于左偏的叠词,考虑右半边的第一个字符
s[mid] ,在左半边寻找一个 s[i] ,使得存在 l , r 使 s[i-l..i+r] = s[mid-l..mid+r] 且 i+r=mid-
l 。对于跨过中间点的左偏叠词,一定能找到这样的 s[i] 。实际上,我们只需要对每个 i ,找到满足
s[i..i+r] = s[mid..mid+r] 的最大的 r 和满足 s[i-l..i] = s[mid-l..mid] 的最大的 l ,判断
s[i..i+r] 和 s[mid-l..mid] 是否能相遇即可。对于右偏叠词,也是类似的做法。

这其实就是在求一些最长公共前缀/后缀,利用Z函数可以加速这个过程,这样单次时间复杂度为

#include <stdio.h>
#include <string.h>
#define MAXN 1000005
char s[MAXN];
int max(int x, int y) {
return x > y ? x : y;
}
void memrev(char *data, int n) {
for (char *p = data, *q = data + n - 1; p < q; ++p, --q) {
char t = *p;
*p = *q;
*q = t;
}
}
void get_z(char s[], int n, int z[]) {
memset(z, 0, sizeof(int) * n);
for (int i = 1, l = 0, r = 0; i < n; ++i) {
if (z[i - l] < r - i + 1) {
z[i] = z[i - l];
} else {
z[i] = max(r - i + 1, 0);
while (i + z[i] < n && s[z[i]] == s[i + z[i]])
z[i]++;
l = i, r = i + z[i] - 1;
}
}
}
int solve(char s[], int n) {
static char s1[MAXN], s2[MAXN], s3[MAXN], s4[MAXN];
static int z1[MAXN], z2[MAXN], z3[MAXN], z4[MAXN];
if (n <= 1) return 0;
if (n == 2) return s[0] == s[1];
int mid = n / 2;
char *u = s, *v = s + mid;
int ulen = mid, vlen = n - mid, len = ulen + vlen + 1;
memcpy(s1, v, vlen), s1[vlen] = ' ', memcpy(s1 + vlen + 1, u, ulen);
memcpy(s2, u, ulen), memrev(s2, ulen);
memcpy(s3, s1, len), memrev(s3, len);
memcpy(s4, v, vlen);
get_z(s1, len, z1), get_z(s2, ulen, z2), get_z(s3, len, z3), get_z(s4, vlen,
z4);
for (int i = mid - 1; i >= 0; --i)
if (s[i] == s[mid] && z1[vlen + 1 + i] + (i ? z2[ulen - i] : 0) >= ulen
- i)
return 1;
for (int i = mid; i < n; ++i)
if (s[i] == s[mid - 1] && z3[n + ulen - i] + (i < n - 1 ? z4[i + 1 -
ulen] : 0) >= i - ulen + 1)
return 1;
return solve(u, ulen) || solve(v, vlen);
}
int main() {
int t;
scanf("%d", &t);
while (t--) {
scanf("%s", s);
int n = strlen(s);
puts(solve(s, n) ? "YES" : "NO");
}
return 0;
}

You might also like