2020第十一屆藍橋杯CC++B組省賽題目(2020.10.17)
前言
最近參加了藍橋杯比賽,現在把題目貼上來
轉載:https://blog.csdn.net/jziwjxjd/article/details/109136535
試題A 門牌製作
【問題描述】
小藍要為一條街的住戶製作門牌號。
這條街一共有 2020 位住戶,門牌號從 1 到 2020 編號。
小藍製作門牌的方法是先製作 0 到 9 這幾個數字字元,最後根據需要將字
符貼上到門牌上,例如門牌 1017 需要依次貼上字元 1、 0、 1、 7,即需要 1 個
字元 0, 2 個字元 1, 1 個字元 7。
請問要製作所有的 1 到 2020 號門牌,總共需要多少個字元 2?
【答案提交】
這是一道結果填空的題,你只需要算出結果後提交即可。本題的結果為一個整數,在提交答案時只填寫這個整數,填寫多餘的內容將無法得分。
答案:624
試題B 既約分數
【問題描述】
如果一個分數的分子和分母的最大公約數是1,這個分數稱為既約分數。例如,3/4 , 5/2 , 1/8 , 7/1都是既約分數。請問,有多少個既約分數,分子和分母都是1 到2020 之間的整數(包括1和2020)?
【答案提交】
這是一道結果填空的題,你只需要算出結果後提交即可。本題的結果為一個整數,在提交答案時只填寫這個整數,填寫多餘的內容將無法得分。
答案:2481215
試題C 蛇形填數
【問題描述】
如下圖所示,小明用從1 開始的正整數“蛇形”填充無限大的矩陣。
容易看出矩陣第二行第二列中的數是5。請你計算矩陣中第20 行第20 列的數是多少?
【答案提交】
這是一道結果填空的題,你只需要算出結果後提交即可。本題的結果為一
個整數,在提交答案時只填寫這個整數,填寫多餘的內容將無法得分。
答案:761
#include <stdio.h> int a[100][100]; int cnt=1; int main() { int i; int x,y; for(i = 1 ; i <= 40; i++) { if(i % 2==1 ) { for(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++; } } printf("%d\n", a[20][20]); return 0; }
試題D 跑步鍛鍊
【問題描述】
小藍每天都鍛鍊身體。
正常情況下,小藍每天跑 1 千米。如果某天是週一或者月初(1 日),為了
激勵自己,小藍要跑 2 千米。如果同時是週一或月初,小藍也是跑 2 千米。
小藍跑步已經堅持了很長時間,從 2000 年 1 月 1 日週六(含)到 2020 年
10 月 1 日週四(含)。請問這段時間小藍總共跑步多少千米?
【答案提交】
這是一道結果填空的題,你只需要算出結果後提交即可。本題的結果為一
個整數,在提交答案時只填寫這個整數,填寫多餘的內容將無法得分。
答案:8879
試題E 七段碼
【問題描述】
小藍要用七段碼數碼管來表示一種特殊的文字。
七段碼上圖給出了七段碼數碼管的一個圖示,數碼管中一共有7 段可以發光的二極體,分別標記為a, b, c, d, e, f, g。小藍要選擇一部分二極體(至少要有一個)發光來表達字元。在設計字元的表達時,要求所有發光的二極體是連成一片的。
例如:b 發光,其他二極體不發光可以用來表達一種字元。
例如:c 發光,其他二極體不發光可以用來表達一種字元。這種方案與上一行的方案可以用來表示不同的字元,儘管看上去比較相似。
例如:a, b, c, d, e 發光,f, g 不發光可以用來表達一種字元。
例如:b, f 發光,其他二極體不發光則不能用來表達一種字元,因為發光的二極體沒有連成一片。
請問,小藍可以用七段碼數碼管表達多少種不同的字元?
【答案提交】
這是一道結果填空的題,你只需要算出結果後提交即可。本題的結果為一
個整數,在提交答案時只填寫這個整數,填寫多餘的內容將無法得分。
答案:80
此題解來自:https://blog.csdn.net/weixin_43989094/article/details/109149148
//a-0,b-1,c-2,d-3,e-4,f-5,g-6
#include<iostream>
#include<cstring>
#include<set>
using namespace std;
int ve[7][7];
bool visit[7];
int ans=0;
set<set<int> > se;
void dfs(int x,set<int> s)
{
if(!se.count(s))
{
se.insert(s);
// set<int>::iterator it;
// for(it=s.begin();it!=s.end();it++)
// {
// cout<<char('a'+*it)<<" ";
// }
// cout<<endl;
ans++;
}
if(s.size()==7)
return ;
for(int j=0;j<7;j++)
{
if(visit[j]||!ve[x][j])
continue;
s.insert(j);
visit[j]=1;
dfs(j,s);
visit[j]=0;
s.erase(j);
}
}
void add(int x,int y)
{
ve[x][y]=1;
ve[y][x]=1;
}
int main()
{
//a的所有邊
add(0,1);
add(0,5);
//b的所有邊
add(1,6);
add(1,2);
//c的所有邊
add(2,6);
add(2,3);
//d的所有邊
add(3,4);
//e的所有邊
add(4,5);
add(4,6);
//f的所有邊
add(5,6);
set<int> s;
for(int i=0;i<=6;i++)
{
s.insert(i);
visit[i]=1;
dfs(i,s);
visit[i]=0;
s.erase(i);
}
cout<<ans<<endl;
}
程式設計題
試題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<stdio.h>
int main()
{
double a1=0,a2=0;
int n,i;
scanf("%d",&n);
for(i=0;i<n;i++){
int s;
scanf("%d",&s);
if(s>=60) a1++;
if(s>=85) a2++;
}
a1=(a1/n+0.005)*100;
a2=(a2/n+0.005)*100;
printf("%d%%\n%d%%",(int)a1,(int)a2);
return 0;
}
試題G 迴文日期
【問題描述】
2020 年春節期間,有一個特殊的日期引起了大家的注意:2020年2月2日。因為如果將這個日期按“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位數表示。
答案
#include <iostream>
#include <iomanip>
using namespace std;
int a[12]={31,28,31,30,31,30,31,31,30,31,30,31};
bool isok(int x)//判斷閏年
{
if( x%400==0 ) return true;
if( x%100==0 ) return false;
if( x%4==0 ) return true;
return false;
}
int main()
{
int x;
cin >> x;
int year=x/10000;//輸入的年
int month=x%10000/100;//輸入的月
int day=x%100;//輸入的日
int flag=0;
int m=year%10*10+year%100/10;//年份對應的迴文月
int n=year/100%10*10+year/1000;//年份對應的迴文日
if(isok(year)) a[1]=29;
else a[1]=28;
if((m>month&&m<13)||(m==month&&n>day&&n<=a[m-1])){//當年對應的迴文日期在輸入的日期之後
cout<<setw(2)<<setfill('0')<<year<<setw(2)<<setfill('0')<<m<<setw(2)<<setfill('0')<<n<<endl;//輸出當年對應的迴文日期
flag=1;
if((year/1000!=year%10)&&(year%100==m%10*10+m/10)&&(m==n)){ //判斷當年對應的迴文日期是否是ABAB BABA型日期
cout<<setw(2)<<setfill('0')<<year<<setw(2)<<setfill('0')<<m<<setw(2)<<setfill('0')<<n<<endl;
return 0;
}
}
while(year<=8999){
year++;
month=year%10*10+year%100/10;//年份迴文月
day=year/100%10*10+year/1000;//年份迴文日
if(month>12||day>31) continue;
if(isok(year)) a[1]=29;
else a[1]=28;
if((flag!=1)&&month>0&&month<13&&day>0&&day<=a[month-1]){//找回文型
cout<<setw(2)<<setfill('0')<<year<<setw(2)<<setfill('0')<<month<<setw(2)<<setfill('0')<<day<<endl;
flag=1;
}
if((year/1000!=year%10)&&(year%100==month%10*10+month/10)&&(month==day)){//找ABABBABA型
cout<<setw(2)<<setfill('0')<<year<<setw(2)<<setfill('0')<<month<<setw(2)<<setfill('0')<<day<<endl;
break;
}
}
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。
答案
#include <bits/stdc++.h>
using namespace std;
const int maxn=2e5+10;
int id[27][maxn],nu[27],b[27];//下標
char a[maxn];
long long ans;
int main()
{
cin >> (a+1);
int len=strlen(a+1);
for(int i=1;i<=len;i++)
id[a[i]-'a'][++nu[a[i]-'a']]=i;//記錄每個字母出現的下標
for(int i=1;i<=len;i++)//計算以i開頭的子串的貢獻
{
int top=0;
for(int j=0;j<=25;j++)//記錄每個字母最快出現在i之後的下標
{
if( id[j][ nu[j] ] >= i )//假如出現最晚的這個字母比i大才去查詢,而且需要是第一次出現
{
int index = lower_bound(id[j],id[j]+1+nu[j],i)-id[j];//二分查詢加速
b[++top] = id[j][index];
}
}
sort(b+1,b+1+top);//對每個字母的出現時間排序
int last = i;
for(int j=2;j<=top;j++)
{
ans += ( b[j]-last )*(j-1) ;
last = b[j];
}
ans += ( len-last+1 )*top;
}
cout << ans;
}
試題I 平面切分
【問題描述】
平面上有 N 條直線,其中第 i 條直線是 y = Ai · x + Bi。
請計算這些直線將平面分成了幾個部分。
【輸入格式】
第一行包含一個整數 N。
以下 N 行,每行包含兩個整數 Ai; Bi。
【輸出格式】
一個整數代表答案。
【樣例輸入】
3
1 1
2 2
3 3
【樣例輸出】
6
【評測用例規模與約定】
對於 50% 的評測用例, 1 ≤ N ≤ 4, −10 ≤ Ai; Bi ≤ 10。
對於所有評測用例, 1 ≤ N ≤ 1000, −100000 ≤ Ai; Bi ≤ 100000。
答案
此題解來自:https://blog.csdn.net/weixin_43989094/article/details/109149148
#include<iostream>
#include<cstring>
#include<set>
using namespace std;
set<pair<double,double> > se;
const int N = 1005;
double A[N];
double B[N];
set<pair<double,double> > s;
set<pair<double,double> >::iterator it;
int main()
{
int n,i,j,x,y;
cin>>n;
for(i=0;i<n;i++)
{
cin>>x>>y;
s.insert(make_pair(x,y));
}
n = s.size();
for(i=0,it=s.begin(),it++;it!=s.end();it++,i++)
{
A[i]=(*it).first;
B[i]=(*it).second;
}
long long ans=2;
for(i=1;i<n;i++)
{
set<pair<double,double> > se;
for(j=i-1;j>=0;j--)
{
double x=(B[j]-B[i])/(A[i]-A[j]);
double y=(A[j]*B[i]-A[i]*B[j])/(A[j]-A[i]);
se.insert(make_pair(x,y));
}
int n2=se.size();
ans+=(n2+1);
}
cout<<ans<<endl;
}
試題J 字串排序
【問題描述】
小藍最近學習了一些排序演算法,其中氣泡排序讓他印象深刻。
在氣泡排序中,每次只能交換相鄰的兩個元素。小藍髮現,如果對一個字串中的字元排序,只允許交換相鄰的兩個字元,則在所有可能的排序方案中,氣泡排序的總交換次數是最少的。
例如,對於字串 lan 排序,只需要 1 次交換。對於字串 qiao 排序,總共需要 4 次交換。
小藍找到了很多字串試圖排序,他恰巧碰到一個字串,需要 V 次交換,可是他忘了把這個字串記下來,現在找不到了。
請幫助小藍找一個只包含小寫英文字母且沒有字母重複出現的字串,對該串的字元排序,正好需要 V 次交換。如果可能找到多個,請告訴小藍最短的那個。如果最短的仍然有多個,請告訴小藍字典序最小的那個。請注意字串中可以包含相同的字元。
【輸入格式】
輸入的第一行包含一個整數V,小藍的幸運數字。
【輸出格式】
題面要求的一行字串。
【樣例輸入】
4
【樣例輸出】
bbaa
【樣例輸入】
100
【樣例輸出】
jihgfeeddccbbaa
【評測用例規模與約定】
對於 30% 的評測用例, 1 ≤ V ≤ 20。
對於 50% 的評測用例, 1 ≤ V ≤ 100。
對於所有評測用例, 1 ≤ V ≤ 10000。
題解一
此題解來自https://blog.csdn.net/jziwjxjd/article/details/109136535
(程式碼在部分資料是錯的,只能算騙分程式碼,寫錯了…)
#include <bits/stdc++.h>
using namespace std;
const int N = 1e4 + 10;
char ans[N], res[N];
int n, len;
bool judge()
{
int i = len;
while(ans[i] == res[i] && i) i--;
return res[i] < ans[i];
}
void dfs(int now, int maxn, int m, int sum) {
if(sum == n)
{
if(m < len || (m == len && judge()))
{
len = m;
for(int i = 1; i <= len; i++) ans[i] = res[i];
}
return;
}
if(now >= 26) return ;
for(int i = 1; i <= maxn; i++)
{
int temp = sum + m * i;
if(temp > n) return ;
res[m + i] = char(now + 'a');
dfs(now + 1, i, m + i, temp);
}
}
int main()
{
len = 0x3f3f3f3f;
scanf("%d", &n);
dfs(0, 8, 0, 0);
for(int i = len; i >= 1; i--)
putchar(ans[i]);
return 0;
}
題解二
此題解來自https://blog.csdn.net/calculate23/article/details/109139748
由於限定的的合法字元只有 26 個字母,假設沒有這個約束可以直接按完全逆序排序使得長度最小,然後在消去部分逆序數。而對於這題,首先字母是可以重複的,並且在長度相同若要保證字典序更小顯然要讓較大的字母數量較少,此時只要讓較小的字母較多即可。
這裡可能會陷入一個可貪心的思維誤區:這也是我下面展示的一個假演算法的例子:錯誤點在字典序最優是一個全域性的特性,區域性字典序最優不能保證全域性字典序最優,所以正解應該是搜尋+剪枝。
假演算法
#include <bits/stdc++.h>
using namespace std;
const int N = (int)1e4+5;
int num[N];
int main() {
int n, m;
int _max, id, len, sum;
scanf("%d", &n);
sum = 0; len = 0;
while (sum < n) {
id = 1;
for (int i = 2; i <= 26; i++) { //找到s最小的點, 如果存在多個取字典序更小的
if (num[i] < num[id]) id = i;
}
sum = sum + len - num[id];
len ++;
num[id] ++;
}
m = sum - n; //注意更新逆序數差值
for (int i = 1; i <= 26; i++) {
if (num[i]) {
_max = i;
}
}
for (int i = _max; i >= 1; i--) {
for (int j = 0; j < num[i]; j++) {
printf("%c", 'a'+i-1);
}
}
printf("\n");
for (int i = 1; i <= m; i++) {
/*
//儘量消去最大的數字,並且逆序數變化得最少,如果變化量相同則選擇字典序較小的方案
id = 0; sum = INT_MAX;
for (int j = 1; j <= 26; j++) {
if (m - i + num[_max] - num[j] >= 0) {
if (sum > num[j] - num[_max] + 1) {
sum = num[j] - num[_max] + 1;
id = j;
}
}
}
if (id) {
num[id] ++;
num[_max] --;
if (!num[_max]) _max--;
i += sum - 1;
printf("~%d\n", id);
continue;
}
*/
for (int j = _max; j >= 1; j--) {
id = 0;
while(num[++id]!= num[j]);
if (id != j) {
num[id] ++;
num[j] --;
break;
}
}
if (!num[_max]) {
_max--;
}
}
for (int i = _max; i >= 1; i--) {
for (int j = 0; j < num[i]; j++) {
printf("%c", 'a'+i-1);
}
}
printf("\n");
return 0;
}
搜尋+剪枝
#include <bits/stdc++.h>
using namespace std;
const int N = (int)1e4+5;
int num[N], res[N];
int n, m, _max, len;
bool judge(int letter) {
int i = 26, j = letter;
while (!res[i]) i--;
if (i == j) {
while (i > 0 && j > 0) {
if (res[i] != num[j]) {
return res[i] > num[j];
} else {
i--; j--;
}
}
}
return i > j;
}
void dfs(int letter, int curlen, int cursum, int l) {
if (cursum > n) return ;
if (letter > _max) return ;
if (curlen > len) return ;
if (curlen == len && cursum != n) return ;
if (letter == _max && cursum != n) return ;
if (cursum == n) {
if (curlen < len || judge(letter)) { //長度減小或字典序減小更新結果
len = curlen;
for (int i = 1; i <= 26; i++) {
res[i] = num[i];
}
}
return ;
}
for (int i = 1; i <= l; i++) {
num[letter + 1] = i;
dfs(letter + 1, curlen + i, cursum + i * curlen, i);
}
num[letter + 1] = 0;
}
int main() {
scanf("%d", &n);
m = 0; len = 0;
while (m < n) {
int id = 1;
for (int i = 2; i <= 26; i++) { //找到s最小的點, 如果存在多個取字典序更小的
if (res[i] < res[id]) id = i;
}
m += len - res[id];
_max = max(_max, id);
len ++; res[id] ++;
}
dfs(0, 0, 0, 10);
for (int i = _max; i >= 1; i--) {
for (int j = res[i]; j > 0; j--) {
printf("%c", i-1+'a');
}
}
printf("\n");
return 0;
}