第十四届蓝桥杯大赛软件赛省赛C/C++大学A组
第十四届蓝桥杯大赛软件赛省赛C/C++大学A组
AB 为填空思维题
C 数论基础题
D 暴力或者区间DP
E 启发式合并模板题
F DFS + 剪枝
G kruskal重构树 + LCA
H 拆位算贡献
I DFS + 剪枝
J 不会
A: 幸运数
题面
本题总分:5 分
【问题描述】
小蓝认为如果一个数含有偶数个数位,并且前面一半的数位之和等于后面一半的数位之和,则这个数是他的幸运数字。例如 2314 是一个幸运数字,因为它有 4 个数位,并且 2 + 3 = 1 + 4 。现在请你帮他计算从 1 至 100000000 之间共有多少个不同的幸运数字。
【答案提交】
这是一道结果填空的题,你只需要算出结果后提交即可。本题的结果为一个整数,在提交答案时只填写这个整数,填写多余的内容将无法得分。
思路
法一
暴力测试每一个数字是否符合要求,本机能跑出正确结果 4430091
法二
注意到数字的最大范围是 1e8,说明符合条件的数字位数最多是 8 位,那么一半就是 4 位
定义数组
枚举前后部分数字的位数
代码
- 暴力做法
void solve(){ int n = 5, a[10] = {}; a[0] = 1; for(int i = 1; i < n; ++ i){ a[i] = a[i - 1] * 10; } const int N = 1e8; int ans = 0; for(int i = 1; i <= N; ++ i){ string ss = to_string(i); int len = ss.size(); if(len % 2) continue; int x = i / a[len / 2], y = i % a[len / 2]; int sumx = 0, sumy = 0; while(x){ sumx += x % 10; x /= 10; } while(y){ sumy += y % 10; y /= 10; } if(sumx == sumy){ ++ ans; } } cout << ans << '\n'; return ; }
- 优化做法
void solve(){ vector cnt(5, vector<int>(50, 0)); for(int i = 1; i < 1e4; ++ i){ int len = 0, sum = 0; for(int j = i; j; j /= 10){ ++ len; sum += j % 10; } ++ cnt[len][sum]; } int ans = 0; for(int i = 1; i <= 4; ++ i){ for(int j = 1; j <= i * 9; ++ j){ for(int k = 1; k <= i; ++ k){ ans += cnt[i][j] * cnt[k][j]; } } } cout << ans << '\n'; return ; }
B: 有奖问答
题面
本题总分:5 分
【问题描述】
小蓝正在参与一个现场问答的节目。活动中一共有 30 道题目,每题只有答对和答错两种情况,每答对一题得 10 分,答错一题分数归零。
小蓝可以在任意时刻结束答题并获得目前分数对应的奖项,之后不能再答任何题目。最高奖项需要 100 分,所以到达 100 分时小蓝会直接停止答题。
已知小蓝最终实际获得了 70 分对应的奖项,请问小蓝所有可能的答题情况有多少种?
【答案提交】
这是一道结果填空的题,你只需要算出结果后提交即可。本题的结果为一个整数,在提交答案时只填写这个整数,填写多余的内容将无法得分。
思路
直接暴搜,同时根据最终 70 分的限制进行剪枝,剩下的题不够理论 70 分就直接结束递归
注意题目的限制条件,一定要看清楚!!!
- 可以在任意时刻结束答题并获得对应的分数,这意味着只要出现 70 分就是一个可以的答题方式
- 达到 100 分时直接停止答题
代码
void solve(){ int n = 30, ans = 0; auto dfs = [&](auto self, int i, int c) -> void { if(c == 70) ++ ans; if(c == 100) return ; if(i > n){ return ; } int yu = n - i; if(c + 10 * yu * 10 >= 70) self(self, i + 1, c + 10); if(yu >= 7) self(self, i + 1, 0); }; dfs(dfs, 0, 0); cout << ans << '\n'; return ; }
C: 平方差
题面
时间限制: 1.0s 内存限制: 256.0MB 本题总分:10 分
【问题描述】
给定
【输入格式】
输入一行包含两个整数
【输出格式】
输出一行包含一个整数满足题目给定条件的
【样例输入】
1 5
【样例输出】
4
【样例说明】
【评测用例规模与约定】
对于 40% 的评测用例,
对于所有评测用例,
思路
对于式子
- 如果说
为奇数,那么可以发现,只要令 即可找到一组可行解 - 如果说
为偶数- 令
,此时 ,说明 4 的倍数一定可以被表示 - 注意到此时得满足式子
右侧包含因子 2。当 是偶数时, 也必然是偶数,此时 也就是 4 的倍数;若 为偶数,此时 也必然是偶数,此时 依然是 4 的倍数。说明偶数中,只有 4 的倍数可以被表示。
- 令
综上,任意区间
阿巴阿巴,蓝桥官方提交最后一个点需要long long 才能过,啊哈哈哈
代码
long long getsum(long long x){ return x / 4 + (x + 1) / 2; } void solve(){ long long l, r, ans = 0; cin >> l >> r; ans = getsum(r) - getsum(l - 1); cout << ans << '\n'; return ; }
D: 更小的数
题面
时间限制: 1.0s 内存限制: 256.0MB 本题总分:10 分
【问题描述】
小蓝有一个长度均为 n 且仅由数字字符 0 ∼ 9 组成的字符串,下标从 0 到n − 1,你可以将其视作是一个具有 n 位的十进制数字 num,小蓝可以从 num 中选出一段连续的子串并将子串进行反转,最多反转一次。小蓝想要将选出的子串进行反转后再放入原位置处得到的新的数字
注意,我们允许前导零的存在,即数字的最高位可以是 0 ,这是合法的。
【输入格式】
输入一行包含一个长度为 n 的字符串表示 num(仅包含数字字符 0 ∼ 9),从左至右下标依次为 0 ∼ n − 1。
【输出格式】
输出一行包含一个整数表示答案。
【样例输入】
210102
【样例输出】
8
【样例说明】
【评测用例规模与约定】
对于 20% 的评测用例,1 ≤ n ≤ 100 ;
对于 40% 的评测用例,1 ≤ n ≤ 1000 ;
对于所有评测用例,1 ≤ n ≤ 5000 。
思路
注意到数据范围,
也可以利用区间 DP 解决这个问题,详见附上的代码
代码
- 暴力
void solve(){ string ss; cin >> ss; int ans = 0; for(int i = 0; i < ss.size(); ++ i){ for(int j = i + 1; j < ss.size(); ++ j){ int l = i, r = j; bool f = false; while(l < r){ if(ss[r] < ss[l]){ f = true; break; }else if(ss[r] > ss[l]) break; ++ l; -- r; } if(f){ ++ ans; } } } cout << ans << '\n'; return ; }
- 再摘记一个区间 DP 思路
void solve(){ string ss; cin >> ss; int ans = 0, n = ss.size(); vector dp(n, vector<int>(n, 0)); for(int len = 2; len <= n; ++ len){ for(int i = 0; i + len - 1 < n; ++ i){ int j = i + len - 1; if(ss[i] > ss[j]) dp[i][j] = 1;// 为 1 表示该区间可以翻转 else if(ss[i] == ss[j]) dp[i][j] = dp[i + 1][j - 1]; ans += dp[i][j]; } } cout << ans << '\n'; return ; }
E: 颜色平衡树
题面
时间限制: 1.0s 内存限制: 256.0MB 本题总分:15 分
【问题描述】
给定一棵树,结点由
如果一棵树中存在的每种颜色的结点个数都相同,则我们称它是一棵颜色平衡树。
求出这棵树中有多少个子树是颜色平衡树。
【输入格式】
输入的第一行包含一个整数
接下来
特别地,输入数据保证
【输出格式】
输出一行包含一个整数表示答案。
【样例输入】
6 2 0 2 1 1 2 3 3 3 4 1 4
【样例输出】
4
【样例说明】
编号为 1, 3, 5, 6 的 4 个结点对应的子树为颜色平衡树。
【评测用例规模与约定】
对于 30% 的评测用例,
对于 60% 的评测用例,
对于所有评测用例,
思路
启发式合并模板题
不会的话可以搜搜网上资料学一学
代码
const int N = 2e5 + 5; int n, c[N]; vector<int> e[N]; int son[N], sz[N], id[N], rid[N]; int ans = 0, idx = 0; map<int, int> cnt; void dfs1(int u, int f){ sz[u] = 1; id[u] = ++ idx; rid[idx] = u; for(auto v : e[u]){ if(v == f) continue; dfs1(v, u); sz[u] += sz[v]; if(sz[son[u]] < sz[v]) son[u] = v; } return ; } void dfs2(int u, int f, bool keep){ for(auto v : e[u]){ if(v == son[u] || v == f) continue; dfs2(v, u, false); } if(son[u]) dfs2(son[u], u, true); for(auto v : e[u]){ if(v == son[u] || v == f) continue; for(int l = id[v], r = id[v] + sz[v]; l < r; ++ l){ ++ cnt[c[rid[l]]]; } } ++ cnt[c[u]]; int s = 0; for(auto [u, v] : cnt){ if(s == 0) s = v; else if(s != v){ s = -1; break; } } if(s != -1) ++ ans; if(!keep) cnt.clear(); return ; } void solve(){ cin >> n; for(int i = 1; i <= n; ++ i){ int col, f; cin >> col >> f; c[i] = col; e[i].push_back(f); e[f].push_back(i); } dfs1(1, 0); dfs2(1, 0, false); cout << ans << '\n'; return ; }
F: 买瓜
题面
时间限制: 1.0s 内存限制: 256.0MB 本题总分:15 分
【问题描述】
小蓝正在一个瓜摊上买瓜。瓜摊上共有
小蓝刀功了得,他可以把任何瓜劈成完全等重的两份,不过每个瓜只能劈一刀。
小蓝希望买到的瓜的重量的和恰好为
请问小蓝至少要劈多少个瓜才能买到重量恰好为
蓝都无法得到总重恰好为
【输入格式】
输入的第一行包含两个整数
第二行包含
【输出格式】
输出一行包含一个整数表示答案。
【样例输入】
3 10 1 3 13
【样例输出】
2
【评测用例规模与约定】
对于 20% 的评测用例,
对于 60% 的评测用例,
对于所有评测用例,
思路
DFS + 剪枝
将所有数字都扩大 2 倍以避免小数的出现
将瓜按照重量从大到小排序,再统计一个后缀和,用于搜索时剪枝
DFS 时有三种选择状态:不选当前瓜、完全选择当前瓜,选择当前瓜的一半
剪枝部分详见代码
代码
void solve(){ ll n, m; cin >> n >> m; m <<= 1; vector<ll> a(n); for(int i = 0; i < n; ++ i){ cin >> a[i]; a[i] <<= 1;// 2 倍为了避免小数的出现 } sort(a.begin(), a.end(), greater<int>()); vector<ll> sum(n + 1); for(int i = n - 1; i >= 0; -- i){// 从大到小排序后计算后缀 sum[i] += sum[i + 1] + a[i]; } int ans = 50; auto dfs = [&](auto self, ll s, int i, int cnt) -> void { if(cnt > ans) return ;// 当前需要次数已经超过目前最优解 if(s == m) ans = cnt; if(i == n || s > m || s + sum[i] < m) return ; self(self, s, i + 1, cnt);// 当前瓜不选 self(self, s + a[i], i + 1, cnt);// 选择当前瓜 self(self, s + a[i] / 2, i + 1, cnt + 1);// 选择当前瓜的一半 }; dfs(dfs, 0, 0, 0); if(ans == 50) ans = -1; cout << ans << '\n'; return ; }
G: 网络稳定性
题面
时间限制: 1.5s 内存限制: 256.0MB 本题总分:20 分
【问题描述】
有一个局域网,由 n 个设备和 m 条物理连接组成,第 i 条连接的稳定性为
对于从设备 A 到设备 B 的一条经过了若干个物理连接的路径,我们记这条路径的稳定性为其经过所有连接中稳定性最低的那个。
我们记设备 A 到设备 B 之间通信的稳定性为 A 至 B 的所有可行路径的稳定性中最高的那一条。
给定局域网中的设备的物理连接情况,求出若干组设备
【输入格式】
输入的第一行包含三个整数 n, m, q ,分别表示设备数、物理连接数和询问数。
接下来 m 行,每行包含三个整数
接下来 q 行,每行包含两个整数
【输出格式】
输出 q 行,每行包含一个整数依次表示每个询问的答案。
【样例输入】
5 4 3 1 2 5 2 3 6 3 4 1 1 4 3 1 5 2 4 1 3
【样例输出】
-1 3 5
【评测用例规模与约定】
对于 30% 的评测用例,n, q ≤ 500,m ≤ 1000 ;
对于 60% 的评测用例,n, q ≤ 5000,m ≤ 10000 ;
对于所有评测用例,
思路
注意到对于一个局域网连通块中的任意两个设备,我们都需要找到其路径途中最小值最大的任意一条路
考虑利用 kruskal 构造一个最大生成树,那么其所选择出来的路径就是我们想要的路径
那么我们会得到一个森林,对于每一棵树,利用树上倍增的思想维护路径上的边权最小值,查询时找 lca 的同时维护最小值即可
难度不大,但是涉及的知识点较多
代码
const int N = 1e5 + 5, M = 3e5 + 5, K = 17, inf = 0x3f3f3f3f; int n, m, q; vector<pair<int, int>> e[N]; bool vis[N]; int fa[N][K], c[N][K], dep[N]; struct edge{ int u, v, w; }p[M]; struct DSU{ int num; vector<int> fa, sz; DSU(int x) : fa(x + 1), sz(x + 1, 1), num(x) { for(int i = 0; i <= x; ++ i) fa[i] = i; } int findfa(int x){ while(x != fa[x]) x = fa[x] = fa[fa[x]]; return x; } int size(int x) { return sz[findfa(x)]; } bool same(int x, int y) { return findfa(x) == findfa(y); } bool merge(int x, int y){ x = findfa(x); y = findfa(y); if(x == y) return false; if(sz[x] < sz[y]) swap(x, y); sz[x] += sz[y]; fa[y] = x; return true; } }; void kruskal(DSU& dsu){// 跑最大生成树 sort(p, p + m, [&](edge& x, edge& y){ return x.w > y.w; }); for(int i = 0; i < m; ++ i){ int u = p[i].u, v = p[i].v, w = p[i].w; if(!dsu.same(u, v)){ dsu.merge(u, v); e[u].push_back({v, w}); e[v].push_back({u, w}); } } return ; } void dfs(int u, int f){ vis[u] = true; dep[u] = dep[f] + 1; fa[u][0] = f; for(int i = 1; i < K; ++ i){ fa[u][i] = fa[fa[u][i - 1]][i - 1]; c[u][i] = min(c[u][i - 1], c[fa[u][i - 1]][i - 1]); } for(auto [v, w] : e[u]){ if(v == f) continue; c[v][0] = w; dfs(v, u); } return ; } int lca(int u, int v){ int ans = inf; if(dep[u] < dep[v]) swap(u, v); for(int i = K - 1; i >= 0; -- i){ if(dep[fa[u][i]] >= dep[v]){ ans = min(ans, c[u][i]); u = fa[u][i]; } if(u == v) return ans; } for(int i = K - 1; i >= 0; -- i){ if(fa[u][i] != fa[v][i]){ ans = min(ans, min(c[u][i], c[v][i])); u = fa[u][i]; v = fa[v][i]; } } ans = min(ans, min(c[u][0], c[v][0])); return ans; } void solve(){ cin >> n >> m >> q; for(int i = 0; i < m; ++ i){ cin >> p[i].u >> p[i].v >> p[i].w; } DSU dsu(n); kruskal(dsu); for(int i = 1; i <= n; ++ i){ if(!vis[i]) dfs(i, 0); } for(int i = 0; i < q; ++ i){ int x, y, ans; cin >> x >> y; if(!dsu.same(x, y)) ans = -1; else ans = lca(x, y); cout << ans << '\n'; } return ; }
H: 异或和之和
题面
时间限制: 1.0s 内存限制: 256.0MB 本题总分:20 分
【问题描述】
给定一个数组
【输入格式】
输入的第一行包含一个整数 n 。
第二行包含 n 个整数
【输出格式】
输出一行包含一个整数表示答案。
【样例输入】
5 1 2 3 4 5
【样例输出】
39
【评测用例规模与约定】
对于 30% 的评测用例,n ≤ 300 ;
对于 60% 的评测用例,n ≤ 5000 ;
对于所有评测用例,
思路
先求得异或前缀和
易知题目应求式子:
拆位考虑这个问题,对于某一个特定的位,仅当当前位上
所以我们可以枚举每一位,统计前缀异或和这一位上 0 和 1 的个数,那么这一位对答案产生的贡献就是
代码
void solve(){ const int N = 20; int n; cin >> n; vector<ll> a(n + 1); for(int i = 1; i <= n; ++ i){ cin >> a[i]; a[i] ^= a[i - 1]; } ll ans = 0; vector cnt(N + 1, vector<ll>(2)); for(int k = 0; k <= N; ++ k){ for(int i = 0; i <= n; ++ i){// 注意从 0 开始 ++ cnt[k][a[i] >> k & 1]; } ans += cnt[k][0] * cnt[k][1] * (1ll << k); } cout << ans << '\n'; return ; }
I: 像素放置
题面
时间限制: 1.0s 内存限制: 256.0MB 本题总分:25 分
【问题描述】
小蓝最近迷上了一款名为《像素放置》的游戏,游戏在一个 n × m 的网格棋盘上进行,棋盘含有 n 行,每行包含 m 个方格。玩家的任务就是需要对这n × m 个方格进行像素填充,填充颜色只有黑色或白色两种。有些方格中会出现一个整数数字 x(0 ≤ x ≤ 9),这表示当前方格加上周围八个方向上相邻的方格(分别是上方、下方、左方、右方、左上方、右上方、左下方、右下方)共九个方格内有且仅有 x 个方格需要用黑色填充。
玩家需要在满足所有数字约束下对网格进行像素填充,请你帮助小蓝来完成。题目保证所有数据都有解并且解是唯一的。
【输入格式】
输入的第一行包含两个整数 n, m,用一个空格分隔,表示棋盘大小。
接下来 n 行,每行包含 m 个字符,表示棋盘布局。字符可能是数字 0 ∼ 9,这表示网格上的数字;字符还有可能是下划线(ASCII 码为 95 ),表示一个不带有数字的普通网格。
【输出格式】
输出 n 行,每行包含 m 个字符,表示答案。如果网格填充白色则用字符 0 表示,如果网格填充黑色则用字符 1 表示。
【样例输入】
6 8 _1__5_1_ 1_4__42_ 3__6__5_ ___56___ _688___4 _____6__
【样例输出】
00011000 00111100 01000010 11111111 01011110 01111110
【样例说明】
上图左是样例数据对应的棋盘布局,上图右是此局游戏的解。例如第 3 行第 1 列处的方格中有一个数字 3 ,它周围有且仅有 3 个格子被黑色填充,分别是第 3 行第 2 列、第 4 行第 1 列和第 4 行第 2 列的方格。
【评测用例规模与约定】
对于 50% 的评测用例,1 ≤ n, m ≤ 5 ;
对于所有评测用例,1 ≤ n, m ≤ 10 。
思路
DFS + 剪枝
代码
void solve(){ int n, m; cin >> n >> m; vector<string> ss(n + 2); vector<vector<int>> f(n + 3, vector<int>(m + 3)); for(int i = 1; i <= n; ++ i){ cin >> ss[i]; ss[i] = ' ' + ss[i]; } auto check = [&](int x, int y) -> bool { if(ss[x][y] == '_') return true; int cnt = 0; for(int i = -1; i <= 1; ++ i){ for(int j = -1; j <= 1; ++ j){ cnt += f[x + i][y + j]; } } if(cnt == ss[x][y] - '0') return true; return false; }; auto dfs = [&](auto self, int x, int y) -> void { if(x == n + 1){// 全放好了,需要检查是否符合最后一行 for(int i = 1; i <= m; ++ i) if(!check(n, i)) return ; // 找到一个答案,输出即可 for(int i = 1; i <= n; ++ i){ for(int j = 1; j <= m; ++ j) cout << (char)(f[i][j] + '0'); cout << '\n'; } return ; } if(y == m){// 到了最后一列,需要换行 f[x][y] = 0; if(x == 1 || (y == 1 && check(x - 1, y)) || (check(x - 1, y - 1) && check(x - 1, y))){ self(self, x + 1, 1);// 换行 } f[x][y] = 1; if(x == 1 || (y == 1 && check(x - 1, y)) || (check(x - 1, y - 1) && check(x - 1, y))){ self(self, x + 1, 1);// 换行 } }else{// 遍历下一列 f[x][y] = 0; if(x == 1 || y == 1 || check(x - 1, y - 1)){ self(self, x, y + 1);// 换行 } f[x][y] = 1; if(x == 1 || y == 1 || check(x - 1, y - 1)){ self(self, x, y + 1);// 换行 } } return ; }; dfs(dfs, 1, 1); return ; }
J: 翻转硬币
题面
时间限制: 3.0s 内存限制: 256.0MB 本题总分:25 分
【问题描述】
给定 n 个按顺序摆好的硬币,一开始只有第 1 个硬币朝下,其他硬币均朝上。你每次操作可以选择任何一个整数 i 并将所有满足 j mod i = 0 的位置 j 的硬币翻转。
求最少需要多少次操作可以让所有硬币都朝上。
【输入格式】
输入一行包含一个整数 n 。
【输出格式】
输出一行包含一个整数表示最少需要的操作次数。
【样例输入 1】
7
【样例输出 1】
6
【样例输入 2】
1131796
【样例输出 2】
688042
【评测用例规模与约定】
对于 30% 的评测用例,
对于 70% 的评测用例,
对于所有评测用例,
思路
难题,日后再说
https://blog.csdn.net/qq_40485202/article/details/137413077?spm=1001.2014.3001.5502
代码
xxx
本文来自博客园,作者:Qiansui,转载请注明原文链接:https://www.cnblogs.com/Qiansui/p/18108915
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!