第十一届蓝桥杯大赛软件类省赛 C/C++ 大学 B 组 试题+题解

文章目录

  • 试题 A. 门牌制作
  • 试题 B:既约分数
  • 试题 C. 蛇形填数
  • 试题 D: 跑步锻炼
  • 试题 E: 七段码
  • 试题 F:成绩统计
  • 试题 G: 回文日期
  • 试题 H: 子串分值和
  • 试题 I: 平面切分
  • 试题 J: 字串排序

试题 A. 门牌制作

题目描述:计算1-2020中出现了多少次2,注意不是多少个数字出现2。
题解:直接写,送分题
C++ 代码:

#include

using namespace std;
int res = 0;  
void count(int x)
{
	while(x)
	{
		if(x % 10 == 2) res ++;
		x /= 10;
	}
}
int main()
{
	for(int i = 0;i <= 2020 ; i ++ )
		count(i);
	cout << res << endl;
	return 0;
}
// 624

试题 B:既约分数

题目描述:求多少个分数使得分子和分母的最大公约数为1,且同时分子和分母均在1-2020之间。
题解: 数论 gcd,补充算法:在algorithm包下有一个函数__gcd(int i,int j) 牛啊,以后再也不傻傻的去写gcd函数了,啦啦啦啦
这里也写一下gcd函数,毕竟比较经典的辗转相减法

int gcd(int a, int b)
{
	if(!b) return a;
	return gcd(b, a % b);
}

C++ 代码:

#include 
#include 

using namespace std;
 
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 开始的正整数“蛇形”填充无限大的矩阵。
1 2 6 7 15 :::
3 5 8 14 :::
4 9 13 :::
10 12 :::
11 :::
:::
容易看出矩阵第二行第二列中的数是 5。请你计算矩阵中第 20 行第 20 列的数是多少 ?
题解: 这题我看大家都是去模拟题目的意思,打印出蛇形矩阵,其实不然,他问的是第20行第20列的数是多少,观察对角线上元素,有一定的规律可循:(前4个对角线上的元素如下)1 5 13 25 可以看出

int res0 = 1;
res1 = 1 = res0 + (1 - 1) * 4;
res2 = 5 = res1 + (2 - 1) * 4;
res3 = 13 = res2 + (3 - 1) * 4;
res4 = 25 = res 3 + (4 - 1) * 4 

找到如下的规律就可直接很快的解决问题了:
C++ 代码:

#include

using namespace std;

int main()
{
	int res = 1; 
	for(int i = 2; i <= 20; i ++ )
	{
		res += 4*(i - 1);
		cout << i << " : " << res << endl; 
	}
	return 0;
}
//761

但是本着精益求精的态度,我们给出模拟蛇形填数的代码
C++ 代码:

#include 

using namespace std;
const int N = 30;

int a[N][N];

int main() 
{

    int n = 21, num = 21 * 21;
    for(int i = 1, cnt = 1; i <= n && cnt <= num; 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++;
        }
    }
    printf("%d\n", a[20][20]);
	return 0;
}
// 761

试题 D: 跑步锻炼

题目描述: 小蓝每天都锻炼身体。
正常情况下,小蓝每天跑 1 千米。如果某天是周一或者月初(1 日)为了激励自己,小蓝要跑 2 千米。如果同时是周一或月初,小蓝也是跑 2千米。小蓝跑步已经坚持了很长时间,从 2000 年 1 月 1 日周(含)到 2020 年10 月 1 日周四(含)。请问这段时间小蓝总共跑步多少千米?

题解: 我们打比赛最喜欢的大模拟题,谁知道那个抽风的出题人
C++代码:

#include 
#include  
#include  

using namespace std;

int mouths[13] = {0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; 
bool isleap(int yr)
{
	return yr % 4 == 0 && yr % 100 != 0 || yr % 400 == 0; 
}

int main()
{
	int res = 0, sum = 0;
	int week = 5;
	for(int y = 2000;y < 2020; y ++ )
	{
		if(isleap(y)) mouths[2] = 29;
		else mouths[2] = 28;
		for(int i = 1; i <= 12; i ++ )
		{
			for(int j = 1;j <= mouths[i]; j ++ )
			{
				if((j == 1 && week == 0) || j == 1 || week == 0) res ++;
				week ++;
				week %= 7;
				sum ++;
			}
		}
	}
	mouths[2] = 29;
	for(int i = 1; i <= 9; i ++ )
	{
		for(int j =1 ; j <= mouths[i]; j ++)
		{
			if((j == 1 && week == 0 ) || j == 1 || week == 0) res ++; 
				
			week ++;
			week %= 7;
			sum ++ ;
		}
	}
	cout << res + sum + 2 << endl;
	return 0;
}
// 8879

看到一个大佬的写法,感觉很nice,代码简洁优雅

#include
#include
#include
#include
using namespace std;

int mouths[13] ={0,31,28,31,30,31,30,31,31,30,31,30,31};

int cnt = 5;    //星期几
int ans = 0;    //答案

bool check(int date)    //检查当前日期是否合法
{
    int year = date/10000;
    int mouth = date%10000/100;
    int day = date%100;
    
    if(mouth<1||mouth>12)return false;
    if(day<1)return false;
    bool leap = (year%4==0&&year%100!=0)||(year%400==0);   //判断是闰年还是平年
    if(mouth!=2&&day>mouths[mouth])return false;    //不是二月份是否合法
    if(mouth==2&&day>mouths[mouth]+leap)return false;   //是二月份是否合法
    return true;
}

int main()
{
    
    int start = 20000101;
    for(int i=start;i<=20201001;i++)
    {
        if(check(i))    //检查i是否合法
        {
            cnt++;  //判断星期几
            ans++;  //累计结果
            int day = i%100;    //计算当月天数
            if(cnt%7==1||day==1)ans++;  //如果是周一或者月初,结果再加1;
        }
    }
    cout<<ans<<endl;
    return 0;
}

我看到有人用excel 解决了这个问题,哈哈哈,本着精益求精的态度,然后小编没学会。
Excel解决,牛啊牛啊

试题 E: 七段码

题目描述:
问题描述:七段数码管的a,b,c,d,e,f,g的七个灯,问你最多可以表示为多少种字符(必须要求灯相连)
第十一届蓝桥杯大赛软件类省赛 C/C++ 大学 B 组 试题+题解_第1张图片
题解: dfs + 并查集,dfs枚举,并查集check
C++ 代码:

#include 
#include 
#include 
#include 

using namespace std;
const int N = 10;

bool st[N];
int ans;
int g[N][N], p[N];

/*
	题目思路:dfs + 并查集
	dfs 枚举所有情况
	并查集 检查是否连通 
*/

void init()
{
	/*
		连边建图, g[i][j] = 1, 表示i和j相邻
		a b c d g f g
		1 2 3 4 5 6 7 	
	*/
	g[1][2] = g[1][6] = 1;
	g[2][1] = g[2][7] = g[2][3] = 1;
	g[3][2] = g[3][4] = g[3][7] = 1;
	g[4][3] = g[4][5] = 1;
	g[5][4] = g[5][6] = g[5][7] = 1;
	g[6][1] = g[6][5] = g[6][7] = 1;
}

int find(int x)
{
	if(x != p[x]) p[x] = find(p[x]);
	return p[x];
} 

void dfs(int u)
{
	if(u > 7)
	{
		//并查集初始化 
		for(int i = 1; i <= 7; i ++ ) p[i] = i;
		//并查集合并 
		for(int i = 1; i <= 7; i ++ )
		{
			for(int j = 1; j <= 7; j ++ )
				if(g[i][j] && st[i] && st[j])
				{
					int px = find(i), py = find(j);
					if(px != py) p[px] = py; 
				}
		}
		//判断集合个数,如果只有一个集合则符合条件 
		int k = 0;
		for(int i = 1; i <= 7; i ++ )
			if(st[i] && p[i] == i) k ++;
		if(k == 1) ans ++;
		return;
	}
	
	//使用当前灯 
	st[u] = true, dfs(u + 1);
	//不使用当前灯 
	st[u] = false, dfs(u + 1);
}

int main()
{
	init();
	dfs(1); 
	cout << ans << endl;
	return 0;
}
// 80 

内心os:终于用到数据结构了qaq

试题 F:成绩统计

题目描述:
小蓝给学生们组织了一场考试,卷面总分为 100 分,每个学生的得分都是一个 0 到 100 的整数。如果得分至少是 60 分,则称为及格。果得分至少为 85 分,则称为优秀。请计算及格率和优秀率,用百分数示,百分号前的部分四舍五入保留整数。

输入描述
输入的第一行包含一个整数 n,表示考试人数。
接下来 n 行,每行包含一个 0 至 100 的整数,表示一个学生的得分。

输出描述
输出两行,每行一个百分数,分别表示及格率和优秀率。百分号前的部分四舍五入保留整数。
样例输入

7
80
92
56
74
88
100
0

样例输出

71%
43%

Hint
【评测用例规模与约定】 对于 50% 的评测用例,1 ≤ n ≤ 100。 对于所有评测用例,1 ≤ n ≤ 10000。

题解: 模拟吧,然后需要注意的就是四舍五入和怎样表示百分数

C++ 代码:

#include 
#include 

using namespace std;
const int N = 10010;

int n; 

int main()
{
	int a = 0, b = 0;
	
	cin >> n;
	int nn = n;
	while(n -- )
	{
		int x; cin >> x;
		if(x >= 60) a ++;
		if(x >= 85) b ++;
	}
	
	int a1 = a * 100 / nn;
	int b1 = b * 100 / nn;
	
	double d1 = a1 * 1.0 / nn * 100 - a1;
	double d2 = b1 * 1.0 / nn * 100 - b1;
	
	if(d1 >= 0.5) a1 ++;
	if(d2 >= 0.5) b1 ++;
	
	printf("%d%%\n", a1);
	printf("%d%%\n", b1);
	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 位数表示。

题解: 太棒了,又是我们最喜爱的大模拟题,哇塞
C++ 代码:

#include 
#include 
#include 

using namespace std;
const int s = 1000, e = 8999;

int n;
vector<int> vec;

// 判断回文日期是否合理 
int mouths[13] = { 0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
bool check(int y, int m, int d)
{
	if(m <= 12 && m >= 1)
	{
		if(y % 4 == 0 && y % 100 != 0 || y % 400 == 0) mouths[2] = 29;
		
		if(mouths[m] >= d) return true;
		else return false; 
	}
	return false;
} 

// 构造回文日期 
void init()
{
	for(int i = s; i <= e; i ++ )
	{
		string s = to_string(i);
		for(int j = 3; j >= 0; j -- ) s += s[j];
		
		int m = (s[4] - '0') * 10 + (s[5] - '0');
		int d = (s[6] - '0') * 10 + (s[7] - '0');
		
		if(check(i, m, d)) vec.push_back(stoi(s));
		mouths[2] = 28;
	}
}

// 二分找到第一个比输入大的回文日期 
int ff(int idx)
{
	int l, r, mid;
	l = 0, r = vec.size();
	while(l < r)
	{
		mid = l + r >> 1;
		if(vec[mid] > idx) r = mid;
		else l = mid + 1;	
	}
	return l;
}

int ab(int idx)
{
	for(int  i = idx; i < vec.size(); i ++ )
	{
		string s = to_string(vec[i]);
		
		if(s[0] == s[2] && s[1] == s[3]) return i;
	}
	return -1;
}
int main()
{
	int n; cin >> n;
	init();
	
	int idx = ff(n);
	cout << vec[idx] << endl;
	
	idx = ab(idx);
	cout << vec[idx] << endl;
	
	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

样例说明

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。
题解: 这个题就是可以看出每个字母只贡献了他出现第一次的哪一个1,所以对于给定字符串的每一个子串,我们只需要考虑在某一个子字符串中某一个字母可以贡献多少次,即:这个字母当前出现的位置i 减去他上一次出现的位置pre[i] 的差 乘上 字符串.size() - i

res += (ll)(i - pre[i]) * (str.size() - i);

至于为什么时乘法,其实可考虑一个例子:

abc 我们来看b
b之前 可以构成子串且包含b的为:b bc 2个 
b之后 可以构成子串且包含b的为:ab b 2个
所以包含b的所有子串:
b ab bc abc为 4个
故为乘法 其实可以看成一个乘法分配律,然后将重复的字符合并为一个
(b, bc) * (ab, b) = ab + abc + b + bc 这个意思

C++代码

#include 
#include 
#include 

using namespace std;
typedef long long LL;
const int N = 100010;

int pre[N];
LL res = 0;
int last[26];
int n;

int main()
{
	string s;
	cin >> s;
	for(int i = 0; i < 26;i ++ ) last[i] = -1;
	for(int i = 0; i < s.size(); i ++ )
	{
		int x = s[i] - 'a';
		pre[i] = last[x];
		last[x] = i;
	}
	
	for(int i = 0; i < s.size(); i ++ )
		res += (LL)(i - pre[i]) * (s.size() - i);
		
	cout << res << endl;
	
	return 0;
}

这是比较巧的一种做法,其实可以dp做;本着精益求精的态度,哈哈哈
C++代码:

#include
#include
using namespace std;
map<char,long long> h;
const long long N=1e6+5;
long long dp[N];
long long p;
string str;
int main(){
    cin>>str;
    int len = str.size();
    dp[0] = 1;
    h[str[0]] = 1;
    for(long long i = 1;i < len; i ++)
    {
        if(h[str[i]])
        {
            p += h[str[i]];
            dp[i] = (i + 1) * (i + 2) / 2 - p + dp[i - 1];
            h[str[i]] = i + 1;
        }
        else
        {
            dp[i] = (i + 2) * (i + 1) / 2 - p + dp[i - 1];
            h[str[i]] = i + 1;
        }

    }
    cout << dp[len - 1] << endl;
    return 0;
}

试题 I: 平面切分

题目描述
平面上有 N 条直线,其中第 i 条直线是 y = Ai· x + Bi。 请计算这些直线将平面分成了几个部分。

输入描述
第一行包含一个整数 N。 以下 N 行,每行包含两个整数 Ai, Bi。

输出描述
一个整数代表答案。

样例输入输出
Input

3
1 1
2 2
3 3

Output

6

评测用例规模与约定
对于 50% 的评测用例,1 ≤ N ≤ 4, − 10 ≤ A i , B i ≤ 10 −10 ≤ A_i, B_i ≤ 10 10Ai,Bi10。 对于所有评测用例,1 ≤ N ≤ 1000, − 100000 ≤ A i , B i ≤ 100000 −100000 ≤ A_i, B_i ≤ 100000 100000Ai,Bi100000

题解:
在纸上画一画其实可以发现,因为我们添加的都是直线,所以:

  • 添加的直线与已有直线平行,也就是斜率相等,那么就会贡献1个平面出来
  • 如果添加的直线与已存在直线有交点那么就会贡献2个平面
    比如样例,三条直线同时交与(-1,0)这一个点,那么第一条直线贡献一个平面,第二直线与第一条直线有交点,贡献2个平面,第三个直线与第一个第二个直线交与同一点所以也贡献2个平面,再加上原先本就存在的一个平面,所以一共6个平面

C++代码:

#include
#include 

using namespace std;
const int N = 1010;
typedef long long LL;
typedef long double LB;
typedef pair<LB, LB> PBB;

LB s[N][2];
LL ans;
bool  st[N];
PBB p;

int main()
{
	int n; cin >> n;
	for(int i = 0; i < n; i ++ )
	{
		cin >> s[i][0] >> s[i][1];
		set<PBB> pp;
		
		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[i][0] * p.first + s[i][1];
			pp.insert(p);
		}
		if(!st[i]) ans += pp.size() + 1;
	}
	cout << ans + 1 << endl;
	return 0;
}

试题 J: 字串排序

题目描述:
小蓝最近学习了一些排序算法,其中冒泡排序让他印象深刻。在冒泡排序中,每次只能交换相邻的两个元素。小蓝发现,如果对一个字符串中的字符排序,只允许交换相邻的两个字符,则在所有可能的排序方案中,冒泡排序的总交换次数是最少的。例如,对于字符串 lan 排序,只需要 1 次交换。对于字符串 qiao 排序,总共需要 4 次交换。小蓝的幸运数字是 V,他想找到一个只包含小写英文字母的字符串,对这个串中的字符进行冒泡排序,正好需要 V 次交换。请帮助小蓝找一个这样的字符串。如果可能找到多个,请告诉小蓝最短的那个。如果最短的仍然有多个,请告诉小蓝字典序最小的那个。请注意字符串中可以包含相同的字符。

题解: dfs + 减枝

C++代码:

#include 

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;
}

总结:自己太菜了

你可能感兴趣的:(算法总结,算法,动态规划,图论)