第十二届蓝桥杯2021年C++A组省赛题解


官方题解蓝桥杯近 3 年省赛真题讲解(C&C++ 大学 A 组)_数据结构 - 蓝桥云课

历届真题蓝桥杯大赛历届真题 - C&C++ 大学 A 组 - 蓝桥云课


考生须知

在这里插入图片描述


试题A:卡片

在这里插入图片描述

#include<bits/stdc++.h>
using namespace std;

int cnt[15];
 

int main()
{
	for(int i = 0 ; i <= 9 ; i ++ ) cnt[i] = 2021;
	
	for(int i = 1 ; ; i ++ )
	{
		int k = i;
		while(k != 0)
		{
			if( cnt[k % 10] != 0 ) cnt[k % 10] --;
			else
			{
				cout << i - 1;
				return 0;
			}
			k /= 10;
		}
	}
}

试题B:直线

在这里插入图片描述

题解

  • 注意map是数组类型,按key值查询value值,因此是二维的,而set是集合,没有value值,因此是一维的。

    • set<pair<double, double> > ss;
      map<pair<double, double>, int> mp; 		//必须要有一个int或者其他类型的value值
      
  • 这道题的本质是对所有的直线进行一个去重处理,因此map和set都可以使用,set更简单点。

  • 需要着重注意的是细节类问题

    • 每一条直线对应一个唯一的斜率和截距,二者都是double类型的,因此最好直接求其结果,不要交替复用,以免精度不够。
    • 坐标是整数值,k和b都是double值,因此不要忘记强转类型。
    • 讨论所有情况中有垂直x轴的直线(斜率不存在),防止除零错误(continue)。

代码(set + map)

//set
#include<bits/stdc++.h>
using namespace std; 

set<pair<double, double> > ss;

pair<int, int> point[500];
int idx;


int main()
{
	for(int i = 0 ; i < 20 ; i ++ )
		for(int j = 0 ; j < 21 ; j ++ )
			point[idx ++] = {i, j};
	
	for(int i = 0 ; i < idx ; i ++ )
		for(int j = i + 1 ; j < idx ; j ++ )
		{
			int x1 = point[i].first, y1 = point[i].second;
			int x2 = point[j].first, y2 = point[j].second;
			if(x2 == x1) continue;
			double k = (double)(y2 - y1)/(x2 - x1);
			double b = (double)(x2*y1 - x1*y2)/(x2 - x1);
			
			ss.insert({k, b});
		}
	
	cout << ss.size() + 20;
	
	return 0;
}

// map
#include<bits/stdc++.h>
using namespace std; 

map<pair<double, double>, int> mp;

pair<int, int> point[500];
int idx;

int ans = 20;

int main()
{
	for(int i = 0 ; i < 20 ; i ++ )
		for(int j = 0 ; j < 21 ; j ++ )
			point[idx ++] = {i, j};
	
	for(int i = 0 ; i < idx ; i ++ )
		for(int j = i + 1 ; j < idx ; j ++ )
		{
			int x1 = point[i].first, y1 = point[i].second;
			int x2 = point[j].first, y2 = point[j].second;
			if(x2 == x1) continue;
			double k = (double)(y2 - y1)/(x2 - x1);
			double b = (double)(x2*y1 - x1*y2)/(x2 - x1);
			
			if(mp[{k, b}] == 0)
			{
				mp[{k, b}] = 1;
				ans ++;
			}
		}
	
	cout << ans;
	
	return 0;
}

试题C:货物摆放

在里插入图片描述

题解

  • 一个数由三个数的乘积组成,求不同乘积方案数。
  • 最暴力的办法应该是三层循环,从1到n遍历,然后统计i*j*k == n的count。然后数据量太大,考虑优化。
  • 首先需要开longlong。
  • 然后应该想到没必要遍历1到n之间的所有数。
  • 应该想到的是不论由几个数相乘而得,其中的每一个数都必然出自他的因数中。因此可以预处理出其所有因数,然后暴力寻找不同组合。

代码

#include<bits/stdc++.h>
using namespace std;

typedef long long ll;

vector<ll> vv;

ll n = 2021041820210418;

ll ans = 0;

int main()
{
	for(ll i = 1 ; i < sqrt(n) ; i ++ )
	{
		if(n % i == 0)
		{
			vv.push_back(i);
			if(n / i != i)
				vv.push_back(n / i);
		}
	}
	
	int len = vv.size();
	for(ll i = 0 ; i < len ; i ++ )
		for(ll j = 0 ; j < len ; j ++ )
			for(ll k = 0 ; k < len ; k ++ )
				if(vv[i] * vv[j] * vv[k] == n) ans ++;
	
	cout << ans;
	
	return 0;
}

试题D:路径

在这里插入图片描述

题解

  • dijkstra算法搜索最短路,邻接矩阵就可以
  • 需要知道 a和b的最小公倍数 = a乘b除以a和b的最大公约数 最大公约数(a, b) = a * b / gcd(a, b)

代码

#include<bits/stdc++.h>
using namespace std;

const int N = 2022;

int g[N][N];

int gcd(int x, int y)
{
	return x == 0 ? y : gcd(y % x, x);
}

int dist[N];
bool st[N];

int dijkstra()
{
	memset(dist, 0x3f, sizeof dist);
	dist[1] = 0;
	
	for(int i = 1 ; i <= 2021 ; i ++ )
	{
		int t = -1;
		for(int j = 1 ; j <= 2021 ; j ++ )
			if(!st[j] && (t == -1 || dist[j] < dist[t]))
				t = j;
		
		st[t] = true;
		
		for(int j = 1 ; j <= 2021 ; j ++ )
			dist[t] = min(dist[t], dist[j] + g[j][t]);
	}
	
	return dist[2021];
}

int main()
{
	memset(g, 0x3f, sizeof g);
	
	for(int i = 1 ; i <= 2021 ; i ++ )
		for(int j = i + 1 ; j <= 2021 ; j ++ )
		{
			if(abs(i - j) <= 21)
			{
				g[i][j] = g[j][i] = i * j / gcd(i, j);
			}
		}
	
	cout << dijkstra();
	
	return 0;
}

试题E:回路计数

在这里插入图片描述

题解

同类题目题解见AcWing 91. 最短Hamilton路径 - AcWing

  • 注意:位运算的优先级较低,因此一定要带括号,或者预先定义一个变量来存储。
  • f[state][j] 表示按state方案走,最终到达j点的所有走法

代码

//881012367360
//集合表示:f[state][j] 表示按state方案走,最终到达j点的所有走法
//			如state = 0001100101,j = 0,则有6种不同走法,CFGA,CGFA,GCFA,GFCA,FGAC,FCGA。(其中第0~n-1个点由对应的A~Z表示)
//属性:count
//初始化:f[1][0] = 1; 表示按state = 000..001时,走到第0个节点的方案是一种,A。(即从0点出发)
//结果,要求从0出发回到0的哈夫曼回路,则需要累加从0出发走所有节点一次后最终停留的节点
//🎈如果需要求从任意点出发,最终回到0的哈夫曼回路,则需要初始化所有可出发点(除去0),然后结果只需要f[(1 << 21)][0]即可
//	反向考虑的话,做法与本题相同

#include <bits/stdc++.h>
using namespace std;

const int N = 21, M = 1 << N;

bool g[N][N];
long long f[M][N];

int main()
{	
	for(int i = 1; i <= 21 ; i ++ )
		for(int j = 1 ; j <= 21 ; j ++ )
			if(__gcd(i, j) == 1) g[i - 1][j - 1] = true;
	
	f[1][0] = 1; 
	
	for(int i = 1 ; i < M ; i ++ )
		for(int j = 0 ; j < 21 ; j ++ )
			if(i >> j & 1)
				for(int k = 0 ; k < 21 ; k ++ )
					if(g[k][j] && (i >> k & 1))
						f[i][j] += f[i - (1 << j)][k];
	
	long long res = 0;
	for(int i = 0 ; i <= 20 ; i ++ )
		res += f[M - 1][i];
	
	cout << res << '\n';
	
	return 0;
}


试题F:砝码称重

在这里插入图片描述
在这里插入图片描述

题解

  • 状态表示f[i, j]
    • 集合: 从前i个砝码中选择重量恰好为j
    • 属性:是否存在
  • 状态计算与划分:
    • 状态划分:每一个砝码都有三种情况
      • 不加入
      • 加入到左边
      • 加入到右边
    • 状态计算:只要当前状态可由三种前导状态计算得来,那么当前状态就是可以实现的,即当前重量的组合是存在的,置为1即可
  • 初始化:dp[0] = 1,表示0重量是可以测得的,其他所有状态均由此转化而来。
  • 结果:前n个砝码中,可以拼凑出的不同重量的种类数。即从1到m(总重)统计可以拼凑重量数。

代码

#include<bits/stdc++.h>
using namespace std;

const int N = 100010;

int n, m;
int w[105];
int dp[100010];

int main()
{
    cin >> n;
    for(int i = 1; i <= n ; i ++ )
    {
        cin >> w[i];
        m += w[i];
    }
    
    dp[0] = 1;
    
    for(int i = 1 ; i <= n ; i ++ )
    	for(int j = m ; j >= 0 ; j -- )
    	{
    		dp[j] = dp[j] || dp[abs(j - w[i])];
    		if(j + w[i] <= m) dp[j] = dp[j] || dp[j + w[i]];
		}
    
    int ans = 0;
    for(int i = 1 ; i <= m ; i ++ )
    	ans += dp[i];
    
    cout << ans;
    
    return 0;
}

试题G:异或数列

在这里插入图片描述
在这里插入图片描述

题解

  • **当从X1⊕X2⊕X3⊕……⊕Xn = 0 的时候,必定是平局。**证明如下:
    • 假设最终两位的数是a和b,那么a⊕b = X1⊕X2⊕X3⊕……⊕Xn = 0, 二者异或为0,说明二者相等,即平局。
  • 然后从高位到低位依次判断,直至分出胜负,决策如下:
    • one表示当前位1的个数,zero表示当前位0的个数,one + zero = n。即n个数的某一位要么是1要么是0。
onezero决策
偶数随便下一位做决定
1随便先手胜利
大于1的奇数偶数先手胜利
大于1的奇数奇数后手胜利
  • 有关后手胜利的解释:后手的人一定会优先使用0,最终先手的人无0可用,只能用1,而被迫做出对自己不利的选择,输掉回合。

代码

#include <iostream>
#include<algorithm>
#include<cstring>
using namespace std;
const int N = 2e5 + 10;

int main()
{
	int T;
	cin >> T;
	while(T -- )
	{
		int n;
		int a[N];
		cin >> n;
		int sum = 0;
		for(int i = 0 ; i < n ; i ++ )
		{
			cin >> a[i];
			sum ^= a[i];
		}
		
		if(sum == 0)
		{
			puts("0");
			continue;
		}
		
		for(int j = 20 ; j >= 0 ; j -- )
		{
			int one = 0, zero = 0;
			for(int i = 0 ; i < n ; i ++ )
			{
				if(a[i] >> j & 1) one ++;
				else zero ++;
			}
			if(one % 2 == 1)
			{
				if(zero % 2 == 1 && one != 1) puts("-1");
				else puts("1");
				break;
			}
		}
	}
	return 0;
}

试题H:左孩子右兄弟

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

代码

#include <iostream>
#include<algorithm>
#include<cstring>
using namespace std;

const int N = 1e5 + 10;

int n;
int h[N], e[N], ne[N], idx, s[N];
int dp[N];

void add(int a, int b)
{
	e[idx] = b, ne[idx] = h[a], h[a] = idx++;
}

void dfs(int u, int far)
{
	int ans = 0;
	for(int i = h[u] ; i != -1 ; i = ne[i])
	{
		int j = e[i];
		if(j == far) continue;
		dfs(j, u);
		ans = max(ans, dp[j] + 1);
	}
	
//寻找兄弟节点需要注意细节问题
//1.根节点:0 
//2.一层节点:父节点出边数 - 1
//3.其他节点:父节点出边数 - 2
//ps:为何减2?(父节点的父节点也算父节点的出边) 
	int cnt = 0;
	if(far)
		for(int i = h[far] ; i != -1 ; i = ne[i] )
			if(e[i] > far && e[i] != u) cnt ++;
	
	dp[u] = ans + cnt;
}

int main()
{	
	memset(h, -1, sizeof h);
	
	cin >> n;
	for(int i = 2 ; i <= n ; i ++ )
	{
		int a;
		cin >> a;
		add(i, a), add(a, i);
	}
	
	dfs(1, 0);
	
	cout << dp[1] << '\n';
	
	return 0;
}


试题I:括号序列

在这里插入图片描述

题解

详细讲解见:AcWing 3420. 括号序列 - AcWing

  • 一个括号序列合法的两个条件
    • 左右括号数量相同。
    • 任意前缀中左括号数不小于右括号数。
#include<bits/stdc++.h>
using namespace std;

typedef long long ll;

const int N = 5010, mod = 1e9 + 7;

int n;
char str[N];
ll f[N][N];

ll solve()
{
	memset(f, 0, sizeof f);
	f[0][0] = 1;
	
	for(int i = 1 ; i <= n ; i ++ )
		if(str[i] == '(')
		{
			for(int j = 1 ; j <= n ; j ++ )
				f[i][j] = f[i - 1][j - 1];
		}
		else
		{
			f[i][0] = (f[i - 1][0] + f[i - 1][1]) % mod;
			for(int j = 1 ; j <= n ; j ++ )
				f[i][j] = (f[i - 1][j + 1] + f[i][j - 1]) % mod;
		}
	
	for(int i = 0 ; i <= n ; i ++ )
		if(f[n][i])
			return f[n][i];
	
	return -1;
}

int main()
{
    cin >> str + 1;
    n = strlen(str + 1);
    
    ll l = solve();
    
	reverse(str + 1, str + n + 1);
	for(int i = 1 ; i <= n ; i ++ )
		if(str[i] == '(') str[i] = ')';
		else str[i] = '(';
		
	ll r = solve();
	
	cout << l * r % mod << "\n";
    
    return 0;
}

试题J:分果果

在这里插入图片描述
在这里插入图片描述

  • 19
    点赞
  • 95
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值