文章目录
注
官方题解:蓝桥杯近 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。
one | zero | 决策 |
---|---|---|
偶数 | 随便 | 下一位做决定 |
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:分果果