2020藍橋杯省賽B組C++(第二場)真題

蔚藍不遠發表於2020-10-31

心得體會

前面總結了很多歷屆的省賽真題,這次終於輪到自己上戰場上體驗真題啦~為此次藍橋杯省賽也是準備了一兩週時間,聽說比較水,題不是很難,會暴力就能拿獎哈哈哈考完確實有點這樣的感覺,但是能把五個填空題萬無一失的做全對也不是一件簡單的事情。先說說自己吧,一週後成績出來還算滿意,在遼寧省排名靠前(省一,拿到國賽資格啦),填空題只做對了前三道…其實挺氣的,剛考完看答案心態沒了,第四個填空題算到10月1日,我不小心算到考試當天10月17日了hhh第五題我是暴力數的,少算了兩種情況,可能是大題寫得還行,初略看了下前三個大題基本滿分,第四個…能力有限,第五個暴力寫了一半,估計能得到一些分。其實總體感覺也一般吧,並沒有達到自己的預期發揮。不多想了,好好放寬心態,準備一下11月14日國賽!
準備了啥?
很多人都說藍橋杯…暴力杯,嗯嗯沒錯大概就是這樣,啊哈哈哈其實就花了幾天時間準備準備就考了,有點別的事情在忙。也就是做了近五年的真題,總結了一下演算法和經常考到的題型等等。想要寫的輕鬆些當然用演算法實現肯定是更好了。
總結需要掌握的基礎演算法:排序、雜湊、遞迴、貪心、二分、雙指標、數學問題還有C++ STL標準模板庫等等
進階的資料結構:棧、佇列、連結串列、深度優先搜尋(DFS)、廣度優先搜尋(BFS)、樹、圖、並查集還有動態規劃等等

歷屆近五年藍橋杯B組省賽真題

試題A 門牌製作(5分)

【問題描述】

小藍要為一條街的住戶製作門牌號。
這條街一共有 2020 位住戶,門牌號從 1 到 2020 編號。
小藍製作門牌的方法是先製作 0 到 9 這幾個數字字元,最後根據需要將字
符貼上到門牌上,例如門牌 1017 需要依次貼上字元 1、 0、 1、 7,即需要 1 個
字元 0, 2 個字元 1, 1 個字元 7。
請問要製作所有的 1 到 2020 號門牌,總共需要多少個字元 2?

【答案提交】

這是一道結果填空的題,你只需要算出結果後提交即可。本題的結果為一個整數,在提交答案時只填寫這個整數,填寫多餘的內容將無法得分。

答案:624

試題B 既約分數(5分)

【問題描述】

如果一個分數的分子和分母的最大公約數是1,這個分數稱為既約分數。例如,3/4 , 5/2 , 1/8 , 7/1都是既約分數。請問,有多少個既約分數,分子和分母都是1 到2020 之間的整數(包括1和2020)?

【答案提交】

這是一道結果填空的題,你只需要算出結果後提交即可。本題的結果為一個整數,在提交答案時只填寫這個整數,填寫多餘的內容將無法得分。

答案:2481215

試題C 蛇形填數(10分)

【問題描述】

如下圖所示,小明用從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 跑步鍛鍊(10分)

【問題描述】

小藍每天都鍛鍊身體。
正常情況下,小藍每天跑 1 千米。如果某天是週一或者月初(1 日),為了
激勵自己,小藍要跑 2 千米。如果同時是週一或月初,小藍也是跑 2 千米。
小藍跑步已經堅持了很長時間,從 2000 年 1 月 1 日週六(含)到 2020 年
10 月 1 日週四(含)。請問這段時間小藍總共跑步多少千米?

【答案提交】

這是一道結果填空的題,你只需要算出結果後提交即可。本題的結果為一
個整數,在提交答案時只填寫這個整數,填寫多餘的內容將無法得分。

答案:8879

試題E 七段碼(15分)

【問題描述】

小藍要用七段碼數碼管來表示一種特殊的文字。
在這裡插入圖片描述
七段碼上圖給出了七段碼數碼管的一個圖示,數碼管中一共有7 段可以發光的二極體,分別標記為a, b, c, d, e, f, g。小藍要選擇一部分二極體(至少要有一個)發光來表達字元。在設計字元的表達時,要求所有發光的二極體是連成一片的。
例如:b 發光,其他二極體不發光可以用來表達一種字元。
例如:c 發光,其他二極體不發光可以用來表達一種字元。這種方案與上一行的方案可以用來表示不同的字元,儘管看上去比較相似。
例如:a, b, c, d, e 發光,f, g 不發光可以用來表達一種字元。
例如:b, f 發光,其他二極體不發光則不能用來表達一種字元,因為發光的二極體沒有連成一片。
請問,小藍可以用七段碼數碼管表達多少種不同的字元?

####【答案提交】
這是一道結果填空的題,你只需要算出結果後提交即可。本題的結果為一
個整數,在提交答案時只填寫這個整數,填寫多餘的內容將無法得分。

答案:80

//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);
		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 成績統計(15分)

【問題描述】

小藍給學生們組織了一場考試,卷面總分為 100 分,每個學生的得分都是
一個 0 到 100 的整數。
如果得分至少是 60 分,則稱為及格。如果得分至少為 85 分,則稱為優秀。
請計算及格率和優秀率,用百分數表示,百分號前的部分四捨五入保留整
數。

【輸入格式】

輸入的第一行包含一個整數 n,表示考試人數。
接下來 n 行,每行包含一個 0 至 100 的整數,表示一個學生的得分。

【輸出格式】

輸出兩行,每行一個百分數,分別表示及格率和優秀率。百分號前的部分
四捨五入保留整數。

【樣例輸入】

7
80
92
56
74
88
100

【樣例輸出】

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 迴文日期(20分)

【問題描述】

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 子串分值(20分)

【問題描述】

對於一個字串 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 平面切分(25分)

【問題描述】

平面上有 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。

#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 字串排序(25分)

【問題描述】

小藍最近學習了一些排序演算法,其中氣泡排序讓他印象深刻。
在氣泡排序中,每次只能交換相鄰的兩個元素。小藍發現,如果對一個字串中的字元排序,只允許交換相鄰的兩個字元,則在所有可能的排序方案中,氣泡排序的總交換次數是最少的。
例如,對於字串 lan 排序,只需要 1 次交換。對於字串 qiao 排序,總共需要 4 次交換。
小藍找到了很多字串試圖排序,他恰巧碰到一個字串,需要 V 次交換,可是他忘了把這個字串記下來,現在找不到了。
請幫助小藍找一個只包含小寫英文字母且沒有字母重複出現的字串,對該串的字元排序,正好需要 V 次交換。如果可能找到多個,請告訴小藍最短的那個。如果最短的仍然有多個,請告訴小藍字典序最小的那個。請注意字串中可以包含相同的字元。

【輸入格式】

輸入的第一行包含一個整數V,小藍的幸運數字。

【輸出格式】

題面要求的一行字串。

【樣例輸入】

4

【樣例輸出】

bbaa

【樣例輸入】

100

【樣例輸出】

jihgfeeddccbbaa

【評測用例規模與約定】

對於 30% 的評測用例, 1 ≤ V ≤ 20。
對於 50% 的評測用例, 1 ≤ V ≤ 100。
對於所有評測用例, 1 ≤ V ≤ 10000。

由於限定的的合法字元只有 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++) {
		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;
	}

相關文章