2020年第十一屆藍橋杯C/C++ B組省賽題解
試題A:門牌制作
【問題描述】
小藍要為一條街的住戶制作門牌號。 這條街一共有 \(2020\) 位住戶,門牌號從 \(1\) 到 \(2020\) 編號。 小藍制作門牌的方法是先制作 \(0\) 到 \(9\) 這幾個數字字符,最后根據需要將字 符粘貼到門牌上,例如門牌 \(1017\) 需要依次粘貼字符 \(1、0、1、7\),即需要 \(1\) 個 字符 \(0\),\(2\) 個字符 \(1\),\(1\) 個字符\(7\)。 請問要制作所有的 \(1\) 到 \(2020\) 號門牌,總共需要多少個字符 \(2\)?
思路:
從\(1\)枚舉到\(2020\),挨個轉化為字符串,統計\(2\)的數目即可,可以直接用\(count\)函數。
代碼:
#include <iostream>
#include <sstream>
#include <string>
#include <algorithm>
using namespace std;
void i2s(int num, string &ss) {
stringstream temps;
temps << num;
temps >> ss;
}
int main() {
int res = 0;
for (int i = 1; i <= 2020; i++) {
string numS;
i2s(i, numS);
res += count(numS.begin(), numS.end(), '2');
}
cout << res << endl;
return 0;
}
答案:624
試題B:既約分數
【問題描述】
如果一個分數的分子和分母的最大公約數是 1,這個分數稱為既約分數。 例如,\(\frac{3}{4}\) , \(\frac{5}{2}\) , \(\frac{1}{8}\), \(\frac{7}{1}\) 都是既約分數。 請問,有多少個既約分數,分子和分母都是 1 到 2020 之間的整數(包括 1 和 2020)?
思路:
枚舉
代碼:
#include <iostream>
#include <sstream>
#include <string>
#include <algorithm>
using namespace std;
typedef long long LL;
LL gcd(LL a, LL b) {
a = abs(a), b = abs(b);
while (b != 0) {
a %= b;
swap(a, b);
}
return a;
}
int main() {
int res = 0;
for (int i = 1; i <= 2020; i++) {
for (int j = 1; j <= 2020; j++) {
if (gcd(i, j) == 1) {
res++;
}
}
}
cout << res << endl;
return 0;
}
答案:2481215
試題C:蛇形填數
如下圖所示,小明用從 1 開始的正整數“蛇形”填充無限大的矩陣。

容易看出矩陣第二行第二列中的數是 5。請你計算矩陣中第 20 行第 20 列 的數是多少?
思路:
找規律:
用\(excel\)做幾行后對角線直接找規律
可以看到 這就是一個公差每次增大4的數列,1與5之間是4,5與13之間是8,13與25之間是12.。。。以此類推即可。
編程實現:
把它看成一個“堆”數,然后第一重循環枚舉的是每一個反對角線,里邊兩重循環,如果是奇數對角線則從右上往左下枚舉,如果是偶數對角線則從左下往右上枚舉。
#include <iostream>
#include <sstream>
#include <string>
#include <algorithm>
using namespace std;
typedef long long LL;
int main() {
int a[45][45];
for (int i = 1, cnt = 1; i <= 45; i++) { //i枚舉的是反對角線
if (i & 1) { //奇數對角線從左向右,偶數對角線從右向左
for (int x = i, y = 1; x >= 1 && y <= i; x--, y++) {
a[x][y] = cnt++;
}
} else {
for (int x = 1, y = i; x <= i && y >= 1; x++, y--) {
a[x][y] = cnt++;
}
}
}
cout << a[20][20] << endl;
return 0;
}
答案:761
試題D:跑步鍛煉
小藍每天都鍛煉身體。 正常情況下,小藍每天跑 \(1\) 千米。如果某天是周一或者月初(\(1\) 日),為了 激勵自己,小藍要跑 \(2\) 千米。如果同時是周一或月初,小藍也是跑 \(2\) 千米。 小藍跑步已經堅持了很長時間,從 \(2000\) 年 \(1\) 月 \(1\) 日周六(含)到 \(2020\) 年 \(10\) 月 \(1\) 日周四(含)。請問這段時間小藍總共跑步多少千米?
思路:
1.對着日歷表數,不過貌似比賽的時候我數錯了。。。。。
2.編程實現:
實在沒想出來怎么模擬這玩意,於是參考了網上的答案,確實厲害,沒有省一的我菜的安詳。
參考博客:https://blog.csdn.net/qq_44378358/article/details/109133954
代碼:
#include <iostream>
using namespace std;
typedef long long LL;
int day[] = {0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
bool isRunYear(int y) {
if ((y % 4 == 0 && y % 100 != 0) || (y % 400 == 0)) {
return true;
}
return false;
}
int getDay(int y, int m) { //獲取當前月有多少天
if (m != 2) {
return day[m];
} else if (isRunYear(y)){
return 29;
} else {
return 28;
}
}
int changeWeek(int &xingQi) {
int x = xingQi;
xingQi++;
if (xingQi == 8) {
xingQi = 1;
}
return x;
}
int main() {
int num = 0; //除了每天必跑1公里之外加練的
int sumday = 0;
int week = 6;
for (int y = 2000; y <= 2020; y++) {
for (int m = 1; m <= 12; m++) {
for (int d = 1; d <= getDay(y, m); d++) {
if (changeWeek(week) == 1 || d == 1) {
num++;
}
sumday++;
if (y == 2020 && m == 10 && d == 1) {
cout << sumday + num << endl;
system("pause");
return 0;
}
}
}
}
return 0;
}
答案:8879
試題\(E\):七段碼
【問題描述】
上圖給出了七段碼數碼管的一個圖示,數碼管中一共有 \(7\) 段可以發光的二 極管,分別標記為 \(a, b, c, d, e, f, g\)。

小藍要選擇一部分二極管(至少要有一個)發光來表達字符。在設計字符 的表達時,要求所有發光的二極管是連成一片的。
例如:\(b\) 發光,其他二極管不發光可以用來表達一種字符。
例如:\(c\) 發光,其他二極管不發光可以用來表達一種字符。
這種方案與上 一行的方案可以用來表示不同的字符,盡管看上去比較相似。
例如:\(a, b, c, d, e\) 發光,\(f, g\) 不發光可以用來表達一種字符。
例如:\(b, f\) 發光,其他二極管不發光則不能用來表達一種字符,因為發光 的二極管沒有連成一片。
請問,小藍可以用七段碼數碼管表達多少種不同的字符?
思路:
參考博客:https://blog.csdn.net/kieson_uabc/article/details/109297342#comments_13672258
運用\(DFS\) + 回溯 + 記錄所有路徑 + \(set\)去重。
自己想到了這個思路了,但是也是做了一下午后參考了一下網上相同思路的答案,終於做了出來。
這個思路建圖是:這樣'\0'也是可以走的,最后記錄路徑的時候只需要刪除空格即可。
a b '\0'
f g c
'\0' e d
我把圖建成了這樣,於是也出現了很多問題,把代碼寫的特別繁瑣,最后也不想改了
0 a 0
f 0 b
1 g 1
e 0 c
0 d 0
還有就是回溯的問題,想用那個\(string\)庫的\(pop.back()\)來進行回溯,但還是因為菜啊!回溯用的不行,最后也只好放棄了,而這個思路用一個\(deleteSpace\)函數每一次都會整體改變字符串,根本不用考慮字符串回溯問題了,讓代碼變的特別簡潔,哎,就這樣吧。
#include <iostream>
#include <string>
#include <unordered_set>
#include <vector>
#include <cstring>
#include <algorithm>
#include <set>
using namespace std;
char r[8];
char g[3][3] = {{'a', 'b','\0'}, {'f', 'g', 'c'}, {'\0', 'e', 'd'}};
set<string> st;
bool vis[3][3];
int cnt = 0;
int dx[8] = {-1, 0, 1, 0}, dy[8] = {0, 1, 0, -1};
void deleteSpace(string ss) {
memset(r, '\0', sizeof r);
int k = 0;
for (int i = 0; i < ss.size(); i++) {
if (ss[i] != '\0') {
r[k++] = ss[i];
}
}
sort(r, r + k);
}
void dfs(int a, int b, string s) {
vis[a][b] = true;
for (int i = 0; i < 4; i++) {
int x = a + dx[i], y = b + dy[i];
if (x >= 0 && x < 3 && y >= 0 && y < 3 && !vis[x][y]) {
deleteSpace(s + g[x][y]);
st.insert(r);
dfs(x, y, r);
}
}
vis[a][b] = false;
}
int main() {
for (int i = 0; i < 3; i++) {
for (int j = 0; j < 3; j++) {
if (g[i][j] != '\0') {
string s = "";
s += g[i][j];
st.insert(s);
dfs(i, j, s);
}
}
}
/*
for (set<string> :: iterator it = st.begin(); it != st.end(); it++) {
cout << *it << endl;
}*/
cout << st.size() << endl;
system("pause");
return 0;
}
試題\(F\):成績統計
【問題描述】
小藍給學生們組織了一場考試,卷面總分為 \(100\) 分,每個學生的得分都是 一個 \(0\) 到 \(100\) 的整數。 如果得分至少是 \(60\) 分,則稱為及格。如果得分至少為 \(85\) 分,則稱為優秀。 請計算及格率和優秀率,用百分數表示,百分號前的部分四舍五入保留整 數。
【輸入格式】
輸入的第一行包含一個整數 \(n\),表示考試人數。 接下來 \(n\) 行,每行包含一個 \(0\) 至 \(100\) 的整數,表示一個學生的得分。
【輸出格式】
輸出兩行,每行一個百分數,分別表示及格率和優秀率。百分號前的部分 四舍五入保留整數。
【樣例輸入】
7
80
92
56
74
88
100
0
【樣例輸出】
71%
43%
【評測用例規模與約定】
對於 \(50%\)的評測用例,\(1 ≤ n ≤ 100\)。 對於所有評測用例,\(1 ≤ n ≤ 10000\)
代碼:
#include <iostream>
using namespace std;
int main() {
double n;
double a = 0, b = 0;
scanf("%lf", &n);
for (double i = 0; i < n; i++) {
double grade;
scanf("%lf", &grade);
if (grade >= 85) {
a++;
}
if (grade >= 60) {
b++;
}
}
printf("%.0f%%\n%.0f%%\n", (double)(a / n)*100, (double)(b / n)*100);
cout << round((a / n) * 100) << "%" << endl; //round是自動四舍五入函數
cout << round((b / n) * 100) << "%" << endl;
return 0;
}
試題\(G\):回文日期
【問題描述】
\(2020\) 年春節期間,有一個特殊的日期引起了大家的注意:\(2020\) 年 \(2\) 月 \(24\) 日。因為如果將這個日期按 \("yyyymmdd"\) 的格式寫成一個 \(8\) 位數是 \(20200202\), 恰好是一個回文數。我們稱這樣的日期是回文日期。 有人表示 \(20200202\) 是 “千年一遇” 的特殊日子。對此小明很不認同,因為 不到 \(2\) 年之后就是下一個回文日期:\(20211202\) 即 \(2021\) 年 \(12\) 月 \(2\) 日。 也有人表示 \(20200202\) 並不僅僅是一個回文日期,還是一個 \(ABABBABA\) 型的回文日期。對此小明也不認同,因為大約 \(100\) 年后就能遇到下一個 \(ABABBABA\) 型的回文日期:\(21211212\) 即 \(2121\) 年 \(12\) 月 \(12\) 日。算不上 “千 年一遇”,頂多算 “千年兩遇”。 給定一個 \(8\) 位數的日期,請你計算該日期之后下一個回文日期和下一個 \(ABABBABA\) 型的回文日期各是哪一天。
【輸入格式】
輸入包含一個八位整數 \(N\),表示日期。
【輸出格式】
輸出兩行,每行 \(1\) 個八位數。第一行表示下一個回文日期,第二行表示下 一個 \(ABABBABA\) 型的回文日期。
【樣例輸入】
20200202
【樣例輸出】
20211202
21211212
【評測用例規模與約定】
對於所有評測用例,\(10000101 ≤ N ≤ 89991231\),保證 \(N\) 是一個合法日期的 \(8\) 位數表示。
思路:
知道真相的我眼淚掉下來,原來這道題\(0\)分了,這竟然跑了大概有\(3\)s這怎么能得分啊,樣例都過不去。然后去網上看好像可以直接打表!我怎么沒想到啊-_-!算了一下符合\(ABABBABA\)格式的還不到100個數,打表豈不是凈賺\(20\)分,說不定就能進國賽了,還有\(ABABBABA\)格式\(A\)與\(B\)不能相等,否則成了\(AAAAAAAA\)格式了,考試的時候也沒想到啊!
然后問了一下大佬,說只需要枚舉年份就可以,我恍然大悟了,馬上花了40分鍾把代碼寫出來,然后秒出結果!
代碼:
#include <iostream>
#include <sstream>
#include <string>
#include <algorithm>
using namespace std;
typedef long long LL;
void i2s(int num, string &s) { //數字轉字符串
stringstream ss;
ss << num;
ss >> s;
}
void s2i(int &num, string s) { //字符串轉數字
stringstream ss;
ss << s;
ss >> num;
}
bool isLeap(int year) {
if ((year % 400 == 0) || (year % 4 == 0 && year % 100 != 0)) {
return true;
}
return false;
}
bool judgeDay(int year, int month, int day) { //閏年非閏年分開判斷
int LeapMonthList[13] = {0, 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
int NoLeapMonthList[13] = {0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
if ( (isLeap(year) && day >= 1 && day <= LeapMonthList[month]) ||
(!isLeap(year) && day >= 1 && day <= NoLeapMonthList[month]) ) {
return true;
}
return false;
}
int main() {
LL n;
cin >> n;
n /= 10000; //取得年份
bool flag = false;
for (int i = n + 1; i <= 8999; i++) {
string num;
i2s(i, num);
//string temp = num;
reverse(num.begin(), num.end());
string pre = num.substr(0, 2), nex = num.substr(2, 4);
int month, day;
s2i(month, pre);
s2i(day, nex);
if (month >= 1 && month <= 12) { //是0-12的月份
if (judgeDay(i, month, day) && !flag) { //判斷天數
cout << i << pre << nex << endl;
flag = true;
}
//判斷ABABBABA
if (judgeDay(i, month, day) && num[0] == num[2] && num[1] == num[3] && num[0] != num[1]) {
cout << i << pre << nex << endl;
break;
}
} else {
continue;
}
}
return 0;
}
考試時候的錯誤代碼:
#include <iostream>
#include <string>
#include <sstream>
#include <ctime>
#include <algorithm>
using namespace std;
typedef long long LL;
void i2s(LL num, string &s) {
stringstream ss;
ss << num;
ss >> s;
}
int main() {
//clock_t start, end;
//start = clock();
LL n;
cin >> n;
int flag1 = 0;
for (LL i = n + 1; i <= 89991231; i++) {
string s;
i2s(i, s);
string s1 = s.substr(0, 4), s2 = s.substr(4, 8);
reverse(s2.begin(), s2.end());
if (s1 == s2 && flag1 == 0) {
flag1 = 1;
cout << i << endl;
}
if (s1 == s2 && s1[0] == s1[2] && s1[1] == s1[3]) {
cout << i << endl;
break;
}
}
//end = clock();
//cout << (double)((end - start) / CLOCKS_PER_SEC) << "s";
return 0;
}
試題\(H\):子串分值和
【問題描述】
對於一個字符串 \(S\),我們定義 \(S\) 的分值 \(f(S)\) 為 \(S\) 中出現的不同的字符個 數。例如 \(f(”aba”) = 2\),\(f(”abc”) = 3\),$ f(”aaa”) = 1$。 現在給定一個字符串 \(S [0..n − 1]\)(長度為 \(n\)),請你計算對於所有 \(S\) 的非空 子串 \(S [i.. j](0 ≤ i ≤ j < n)\),\(f(S [i.. j])\) 的和是多少。
【輸入格式】
輸入一行包含一個由小寫字母組成的字符串 \(S\)。
【輸出格式】
輸出一個整數表示答案。
【樣例輸入】
ababc
【樣例輸出】
28
【樣例說明】
子串 f值
a 1
ab 2
aba 2
abab 2
ababc 3
b 1
ba 2
bab 2
babc 3
a 1
ab 2
abc 3
b 1
bc 2
c 1
【評測用例規模與約定】
對於 \(20%\) 的評測用例,\(1 ≤ n ≤ 10\);
對於 \(40%\) 的評測用例,\(1 ≤ n ≤ 100\);
對於 \(50%\) 的評測用例,\(1 ≤ n ≤ 1000\);
對於 \(60%\) 的評測用例,\(1 ≤ n ≤ 10000\);
對於所有評測用例,\(1 ≤ n ≤ 100000\)。
思路:
參考博客:https://blog.csdn.net/weixin_45483201/article/details/109137296?utm_source=app
考場上做的時候直接\(O(n^3)\),然后用把切割出來的子串放到\(set\)中去重,然后取\(set.size()\),大概8分左吧。
這題算是一道挺不錯的思維題吧,也是在理解了挺久,復雜度直接到\(O(n)\)
-
首先,在本題中,如果一個字符串中無重復字母,則把它定義成無重復字符串。
-
我們可以直接認為對於每一個字符串,只有第一次在這個字符串中出現的字母對它所能形成的無重復字符串有貢獻,例如對於"aba",那么它所能行成的無重復字符串為"ab",則我們就認為只是第一次出現的\(a\)和第一次出現的\(b\)對這個無重復字符串有貢獻。其實仔細想一想,母串所能形成的每個子串中不同字母的數量和,就等價於母串中每個字母所能為母串貢獻的無重復字符串數量和,(即每個字母能在多少個字符串中出現第一次)。
-
然后就想到枚舉每一個字母,統計它所能貢獻的無重復字符串的數量。定義\(last[i]\)表示字符\(i\)在當前枚舉點之前最后一次出現的位置,顯然這個字母所能貢獻的無重復字符串的左端點可以出現在\([last[i] + 1, now]\),右端點就在\([now, n]\)之中,因此這樣我們就是就能做到當前(類)字母只作用沒有被當前(類)字母作用過的串,當前字母作用不到以它后面任意字母開頭的所有子串,因此數量就是:
代碼:
#include <iostream>
#include <cstring>
using namespace std;
typedef long long LL;
const int N = 1e5 + 10;
char str[N];
int last[30];
int main() {
scanf("%s", str + 1);
int n = strlen(str + 1);
LL res = 0;
for (int i = 1; i <= n; i++) {
res += 1ll * (i - last[str[i] - 'a']) * (n - i + 1);
last[str[i] - 'a'] = i;
}
printf("%lld\n", res);
return 0;
}
試題I:平面切分
【問題描述】
平面上有 \(N\) 條直線,其中第 \(i\) 條直線是 \(y = A_i · x + B_i\)。 請計算這些直線將平面分成了幾個部分。
【輸入格式】
第一行包含一個整數 \(N\) 以下 \(N\) 行,每行包含兩個整數 \(A_i , B_i\)。
【輸出格式】
一個整數代表答案。
【樣例輸入】
3
1 1
2 2
3 3
【樣例輸出】
6
【評測用例規模與約定】
對於 \(50%\)的評測用例,\(1 ≤ N ≤ 4\), \(−10 ≤ A_i , B_i ≤ 10\)。 對於所有評測用例,\(1 ≤ N ≤ 1000, −100000 ≤ A_i,B_i ≤ 100000\)。
思路:
原創作者:https://blog.csdn.net/weixin_44564818/article/details/109296327
在一個平面內,如果所有的直線互不相交,則每添加一條直線,就會額外增加一個平面,即每添加一條非重邊,非重邊自身就會貢獻一個平面(自身);當這條直線與當前平面內的已有直線每產生一個不同位置的交點,這條直線對平面的總數量的貢獻就額外多增加一個。
每添加一條直線時設置一個空\(set\),將當前直線與當前平面內所有直線的交點\((x, y)\)存入\(set\),如果:
- 如果新新添加的一條直線不是重邊,如果平行,那不用計算交點因此也不用存入\(set\), 直接\(continue\)
- 如果新添加直線不是重邊,如果與平面內直線相交,那么計算交點存入\(set\),那么這條直線對平面的貢獻個數就是\(set.size() + 1\),最后判斷如果此直線沒有與任何一條原有直線重合則計入答案。
這里用\(long double\)的原因應該是為了防止精度丟失吧。
代碼:
#include <iostream>
#include <set>
using namespace std;
const int N = 1e3 + 10;
typedef long long LL;
typedef long double LD;
LD s[N][2];
LL res = 0;
bool st[N];
pair<LD, LD> p; //用來記錄交點
int main() {
int n;
cin >> n;
for (int i = 0; i < n; i++) {
cin >> s[i][0] >> s[i][1];
set<pair<LD, LD>> points;
for (int j = 0; j < i; j++) {
if (st[j]) continue; //說明是重邊 直接忽略
if (s[i][0] == s[j][0]) { //如果兩條直線斜率相等,判斷平行還是重合
if (s[i][1] == s[j][1]) { //如果新添加的直線是重邊 退出循環
st[i] = true;
break;
} else {
continue;
}
}
p.first = (s[j][1] - s[i][1]) / (s[i][0] - s[j][0]);
p.second = s[j][0] * p.first + s[j][1];
points.insert(p);
}
if (!st[i]) res += points.size() + 1; //如果st[i]不是重邊 計入答案
}
cout << res + 1 << endl;
return 0;
}
\(J\)題待補
