- 题解
第十四届蓝桥杯省赛(C/C++ B组)题解
- 2023-5-6 19:32:26 @
前言
无 。
思想及代码来源:2023年第十四届蓝桥杯软件类C/C++B组省赛真题全程手写代码完全讲解
A:日期统计
思路
DFS:
爆搜, 表示枚举数组的第 个位置,日期的第 个位置,当前日期为 。
搜完时 (即 ) 检验下日期是否合法且不重复,满足则 。
参考代码
#include<bits/stdc++.h>
using namespace std;
int a[110], ans;//ans记录有多少个不同日期
bool st[20240000];
bool check(int date)
{
if(st[date]) return false;
st[date] = 1;
int m = date / 100 % 100;//取月份
int d = date % 100;//取日期
if(m < 1 || m > 12) return false;//月份非法
if(m == 1 || m == 3 || m == 5 || m == 7 || m == 8 || m == 10 || m == 12){
if(d >= 1 && d <= 31) return true;
}
else if(m == 2){
if(d >= 1 && d <= 28) return true;
}
else{
if(d >= 1 && d <= 30) return true;
}
return false;
}
void dfs(int x, int pos, int date)
{
if(x == 100) return;
if(pos == 8){
if(check(date))//日期合法
++ans;
return;
}
if( (pos == 0 && a[x] == 2) ||
(pos == 1 && a[x] == 0) ||
(pos == 2 && a[x] == 2) ||
(pos == 3 && a[x] == 3) ||
(pos == 4 && a[x] >= 0 && a[x] <= 1) ||
(pos == 5 && a[x] >= 0 && a[x] <= 9) ||
(pos == 6 && a[x] >= 0 && a[x] <= 3) ||
(pos == 7 && a[x] >= 0 && a[x] <= 9)
)
dfs(x + 1, pos + 1, date * 10 + a[x]);
dfs(x + 1, pos, date);
}
int main()
{
for(int i = 0; i < 100; ++i)
cin >> a[i];
dfs(0, 0, 0);//第几个下标,子序列的第几位,日期
cout << ans << "\n";
return 0;
}
B:01串的熵
思路
观察可发现:
假设字符串 ,,假设有 个 , 个 ,则可得出如下式子:
$H(S) = -u * \dfrac{u}{n} * \log_2(\dfrac{u}{n}) - v * \dfrac{v}{n} *log_2(\dfrac{v}{n})$
有了上述式子,我们可以枚举字符串 中 的个数,且由于 出现的次数比 少,所以只需要枚举长度 的一半即可,枚举时根据式子算出对应的熵 ,如果算出的熵 和题目所给出的信息熵 差别 < ,即是我们所找的答案。
参考代码
#include<bits/stdc++.h>
using namespace std;
using db = long double;
const int N = 23333333;
const db eps = 1e-4;
const db ans = 11625907.5798;
int main()
{
for(int v = 0; v <= N / 2; ++v){
int u = N - v;
db num = (-1.0 * u * u / N * log2(1.0 * u / N)) - 1.0 * v * v / N * log2(1.0 * v / N);
if(fabs(num - ans) < eps){
cout << v << "\n";
break;
}
}
return 0;
}
C:冶炼金属
思路
方法1(暴力枚举):
设转化率为 ,只能练出 个金属,所以有
,
上式整理得:
最后多组数据取一个交集即可
方法2(二分答案):
做两次二分,每次
二分出 的最大值即是 ,二分出的 的最小值即是
参考代码
方法1(暴力枚举):
#include<bits/stdc++.h>
using namespace std;
int main()
{
int n, mn = -1, mx = 2e9 + 10;//最小值和最大值
cin >> n;
while(n--){
int a, b, l, r;
cin >> a >> b;
l = a / (b + 1) + 1, r = a / b;
mn = max(mn, l), mx = min(mx, r);//取交集,缩区间
}
cout << mn << " " << mx << "\n";
return 0;
}
方法2(二分答案):
#include<bits/stdc++.h>
using namespace std;
const int N = 10010;
int n, a[N], b[N];
bool check_min(int v)
{
for(int i = 0; i < n; ++i)
if(a[i] / v > b[i])
return false;
return true;
}
bool check_max(int v)
{
for(int i = 0; i < n; ++i)
if(a[i] / v < b[i])
return false;
return true;
}
int main()
{
cin >> n;
for(int i = 0; i < n; ++i)
cin >> a[i] >> b[i];
int l = 1, r = 1e9;
int mn, mx;
while(l <= r){
int mid = l + r >> 1;
if(check_min(mid)){
mn = mid;
r = mid - 1;
}
else l = mid + 1;
}
l = 1, r = 1e9;
while(l <= r){
int mid = l + r >> 1;
if(check_max(mid)){
mx = mid;
l = mid + 1;
}
else r = mid - 1;
}
cout << mn << " " << mx << "\n";
return 0;
}
D:飞机降落
思路
DFS:
类似于全排列的思想,用一个 变量记录当前是否有解, 数组表示当前飞机是否起飞了。
枚举每一架飞机,如果没起飞过,且当前时间 ,即不晚于第 架飞机的最晚起飞时间,则起飞第 架飞机,起飞后记得恢复现场。
参考代码
#include<bits/stdc++.h>
using namespace std;
const int N = 20;
int n, t[N], d[N], l[N];
bool st[N], flag;//飞机i是否起飞过,当前是否有解
void dfs(int x, int time)
{
if(flag) return;//有解直接剪枝
if(x == n){
flag = true;
return;
}
for(int i = 1; i <= n; ++i)
if(!st[i] && time <= t[i] + d[i]){//没起飞过,且当前时间没有晚于第i架飞机的最晚起飞时间
st[i] = true;
dfs(x + 1, max(t[i], time) + l[i]);//枚举下一架飞机
if(flag) return;//搜完后都有解的话直接return掉
st[i] = false;//恢复现场
}
}
void solve()
{
cin >> n;
flag = false;//多组测试数据复原
for(int i = 1; i <= n; ++i){
cin >> t[i] >> d[i] >> l[i];
st[i] = false;//多组测试数据复原
}
dfs(0, 0);//0时第0架飞机
if(flag) puts("YES");
else puts("NO");
}
int main()
{
int t;
cin >> t;
while(t--)
solve();
return 0;
}
E:接龙数列
思路
DP:
先求出最大接龙数列的长度 ,最后用总长度 减去 即是我们所求的最少删除个数。
状态表示:
二维:,表示前 个数中,以数字 结尾的最长接龙数列的长度。
一维:,表示以数字 结尾的最长接龙数列的长度。
状态转移方程:
为数字的最高位, 为数字的最低位。
例:。
二维:
一维:
方程解释:
表示要么没接上,要么是接上了以数字 结尾的最长接龙数列,且现在接龙数列的结尾更新为 ,最长接龙数列长度 + 1。
参考代码
#include<bits/stdc++.h>
using namespace std;
int n, dp[10];
int main()
{
int n, mx = -1;
cin >> n;
string s;
for(int i = 0; i < n; ++i){
cin >> s;
int x = s[0] - '0', y = s.back() - '0';//最高位,最低位
dp[y] = max(dp[y], dp[x] + 1);
mx = max(mx, dp[y]);//不断更新最长接龙数列的最大长度
}
cout << n - mx << "\n";
return 0;
}
F:岛屿个数
思路
BFS:
本题难点在于如何判断一个岛屿是否为子岛屿。
判断是否为子岛屿:
即通过走水(八连通,即八个方向),判断能否走到边界,能则不为子岛屿,否则为子岛屿。
遍历整个地图,如果当前点没走过且是陆地(字符 '1'),则走这个岛屿,并标记走的所有点,然后再判断这个点是否为子岛屿,如果不为子岛屿则 ++ans,ans记录岛屿总个数。
参考代码
#include<bits/stdc++.h>
#define x first
#define y second
using namespace std;
using PII = pair<int, int>;
const int N = 60;
char mp[N][N];
bool st[N][N], used[N][N];
int dx[] = {0, -1, 0, 1, -1, -1, 1, 1};
int dy[] = {1, 0, -1, 0, 1, -1, -1, 1};
int n, m;
void bfs_col(int x, int y)
{
queue<PII> q;
q.push({x, y});
st[x][y] = true;
while(!q.empty()){
auto t = q.front();
q.pop();
for(int i = 0; i < 4; ++i){
int tx = t.x + dx[i], ty = t.y + dy[i];
if(tx < 0 || tx >= m || ty < 0 || ty >= n || st[tx][ty] || mp[tx][ty] == '0')
continue;
st[tx][ty] = true;
q.push({tx, ty});
}
}
}
bool bfs_out(int x, int y)//判断是否能逃出去,即往水路走,八连通
{
for(int i = 0; i < m; ++i)//清空used数组
for(int j = 0; j < n; ++j)
used[i][j] = false;
queue<PII> q;
q.push({x, y});
used[x][y] = true;
while(!q.empty()){
auto t = q.front();
q.pop();
if(t.x == 0 || t.x == m - 1 || t.y == 0 || t.y == n - 1)
return true;
for(int i = 0; i < 8; ++i){
int tx = t.x + dx[i], ty = t.y + dy[i];
if(tx < 0 || tx >= m || ty < 0 || ty >= n || used[tx][ty] || mp[tx][ty] == '1')
continue;
used[tx][ty] = true;
q.push({tx, ty});
}
}
return false;
}
void solve()
{
scanf("%d%d", &m, &n);//m行n列
for(int i = 0; i < m; ++i){//每组测试数据清空st数组
scanf("%s", mp[i]);
for(int j = 0; j < n; ++j)
st[i][j] = false;
}
int ans = 0;
for(int i = 0; i < m; ++i)
for(int j = 0; j < n; ++j)
if(!st[i][j] && mp[i][j] == '1'){
bfs_col(i, j);
if(bfs_out(i, j))//判断当前点是否能逃跑
++ans;
}
printf("%d\n", ans);
}
int main()
{
int t;
scanf("%d", &t);
while(t--)
solve();
return 0;
}
G:子串简写
思路
前缀和、双指针:
指针 从 开始扫,指针 从 0 开始,每次 与 的距离都为 ,扫的时候统计有多少个 并用 存储,如果 , 则 ,最终 即为所求答案,记得开 ,可能会爆 。
参考代码
#include<bits/stdc++.h>
using namespace std;
using LL = long long;
int main()
{
int k, cnt = 0;
string s;
char c1, c2;
cin >> k >> s >> c1 >> c2;
int n = s.size();
LL ans = 0;
for(int i = k - 1, j = 0; i < n; ++i, ++j){
if(s[j] == c1) ++cnt;//借助前缀和思想, cnt统计前j个数中总共有多少个c1
if(s[i] == c2) ans += cnt;
}
cout << ans << "\n";
return 0;
}
H:整数删除
思路
链表、优先队列:
链表维护每个结点的相邻结点的下标,优先队列存取数值和它对应的下标,这里先开了个大根堆,但为了找最小值,此处存数值和下标时存的都是负数,这样可以转化为小根堆,此时优先队列的队首即是最小值。
模拟删数过程,每次取优先队列队首,取的时候都要乘个 -1 才能转化为原来的数值。
参考代码
#include<bits/stdc++.h>//链表+优先队列
#define val first//数值
#define pos second//下标
using namespace std;
using LL = long long;
using PLI = pair<LL, int>;//数值-下标
const int N = 500010;
LL a[N];
int n, k, pre[N], nxt[N];;
priority_queue<PLI> q;//默认大根堆,存对应负数即可转换为小根堆
int main()
{
scanf("%d%d", &n, &k);
for(int i = 1; i <= n; ++i){
scanf("%lld", &a[i]);
pre[i] = i - 1;//存前驱结点的编号
nxt[i] = i + 1;//存后继节点的编号
q.push({-a[i], -i});
}
pre[1] = -1;
nxt[n] = -1;
while(k--){
PLI now;
do{
now = q.top();//取队首
q.pop();//删除队首
now.val = -now.val, now.pos = -now.pos;//还原数据
}while(a[now.pos] != now.val);//如果满足while条件则表明当前数据已经被删除,则继续循环
int PRE = pre[now.pos], NXT = nxt[now.pos];
if(PRE != -1){//当前点不是左端点,即左边还有点
a[PRE] += now.val;//更新点
q.push({-a[PRE], -PRE});//放入优先队列
nxt[PRE] = NXT;
}
if(NXT != -1){
a[NXT] += now.val;
q.push({-a[NXT], -NXT});
pre[NXT] = PRE;//当前结点now的后继结点,更新它的前驱为pre[now.pos]
}
a[now.pos] = -1;//取了当前点后,将它的值置为-1,表示改点被删除了
}
for(int i = 1; i <= n; ++i)
if(a[i] != -1)
printf("%lld ", a[i]);
return 0;
}
2 条评论
-
ww20020402 LV 6 SU @ 2023-5-8 0:09:23
补充 I
lca
板子,求删除两点距离就是如果删除第一个,那么只用删除他与第二个差值即可,删除第k
个就删除k - 1
与k
即可,其余情况删除1~i 和 i ~i + 1
加上i - 1 ~ i + 1
即可。因为我们会少算了一个从i - 1
到i + 1
的和,多算了1 ~ i 和 i ~ i + 1
的和,因为这条路径没了,预处理一下所有点长度即可,注意开long long
附
AC
代码:#pragma GCC optimize(3,"Ofast","inline") #include <bits/stdc++.h> #define ios_close std::ios::sync_with_stdio(false), std::cin.tie(nullptr), std::cout.tie(nullptr) using ll = long long; using ull = unsigned long long; using i128 = __int128; #define Pi acos(-1.0); #define PINF 0x3f3f3f3f #define NINF -0x3f3f3f3f const int N = 100010; struct Node { int u, v; Node(int _u, int _v) : u(_u), v(_v) {} }; std::vector<std::vector<int>> fa(N, std::vector<int>(21)); std::vector<int> deep(N); std::vector<Node> edges[N]; std::vector<ll> w(N); std::vector<ll> v(N); std::vector<int> ans(N); int n, k; void dfs(int u, int f){ for(auto x : edges[u]){ if(x.u == f){ continue; } deep[x.u] = deep[u] + 1; fa[x.u][0] = u; w[x.u] = 1LL * x.v; dfs(x.u, u); } } void dfs_init(int u, int f){ for(auto x : edges[u]){ if(x.u == f){ continue; } v[x.u] = 1LL * v[u] + w[x.u]; dfs_init(x.u, u); } } void init(){ for(int i = 1; i <= 20; i ++ ){ for(int j = 1; j <= n; j ++ ){ if(fa[j][i - 1]){ fa[j][i] = fa[fa[j][i - 1]][i - 1]; } } } } int lca(int u, int v){ if(deep[u] < deep[v]){ std::swap(u, v); } int t = deep[u] - deep[v]; for(int i = 0; i <= 20; i ++ ){ if((t >> i) & 1){ u = fa[u][i]; } } if(u == v){ return u; } for(int i = 20; i >= 0; i -- ){ if(fa[u][i] != fa[v][i]){ u = fa[u][i]; v = fa[v][i]; } } return fa[u][0]; } void solve(){ std::cin >> n >> k; for(int i = 1; i <= n - 1; i ++ ){ int x, y, v; std::cin >> x >> y >> v; edges[x].push_back({y, v}); edges[y].push_back({x, v}); } dfs(1, 0); init(); dfs_init(1, 0); for(int i = 1; i <= k; i ++ ){ std::cin >> ans[i]; } ll sum = 0; for(int i = 1; i < k; i ++ ){ sum += v[ans[i]] + v[ans[i + 1]] - 2 * v[lca(ans[i], ans[i + 1])]; } for(int i = 1; i <= k; i ++ ){ if(i == 1){ std::cout << sum - (v[ans[i]] + v[ans[i + 1]] - 2 * v[lca(ans[i], ans[i + 1])]) << ' '; } else if(i < k){ ll fa1 = lca(ans[i - 1], ans[i]); ll fa2 = lca(ans[i], ans[i + 1]); ll fa3 = lca(ans[i - 1], ans[i + 1]); ll vfa1 = v[ans[i - 1]] + v[ans[i]] - 2 * v[fa1]; ll vfa2 = v[ans[i]] + v[ans[i + 1]] - 2 * v[fa2]; ll vfa3 = v[ans[i - 1]] + v[ans[i + 1]] - 2 * v[fa3]; std::cout << sum - vfa1 - vfa2 + vfa3 << ' '; } else { std::cout << sum - (v[ans[i]] + v[ans[i - 1]] - 2 * v[lca(ans[i], ans[i - 1])]); } } } int main(){ #if 1 ios_close; #endif #if 0 freopen(".in", "r", stdin); freopen(".out", "w", stdout); #endif int T = 1; #if 0 std::cin >> T; #endif while(T -- ){ solve(); } return 0; }
-
2023-5-7 14:45:03@
补充
说一下
J
,J
是我认为唯一一个有意思的题目,砍树
。题目说对于一些数列,我们要想办法砍掉他们,使得
m
对数字相互独立,也就是不连通。考虑树的性质,树上任意两点间有且只有一条简单路径
,经过u v
两点的路径一定也是唯一的,所以题目就变成了是否有某条边在m
对元素之间被经过了m
次,因为至少是m
,不会小于m
,但是可能会大于m
,大于m
的话就是说你切断了也没有,一定也有别的路径符合条件,所以很自然的想到了我们要统计一些每对数字之间边的经过次数。每经过一条边就让这条边的长度+1
,最后看一些边有没有被经过m
次即可。很显然复杂度是超时。考虑在统计边的次数的时候,我们可以用树上差分优化,
cnt[u] ++ cnt[v] -- cnt[lca] -= 2
,令表示 和它父亲的那条边即可。附
AC
代码:#pragma GCC optimize(3,"Ofast","inline") #include <bits/stdc++.h> #define ios_close std::ios::sync_with_stdio(false), std::cin.tie(nullptr), std::cout.tie(nullptr) using ll = long long; using ull = unsigned long long; using i128 = __int128; #define Pi acos(-1.0); #define PINF 0x3f3f3f3f #define NINF -0x3f3f3f3f const int N = 100010; std::vector<std::vector<int>> fa(N, std::vector<int>(21)); std::vector<int> cnt(N); std::vector<std::pair<int, int>> edges[N]; std::vector<int> deep(N); std::map<int, int> map; int n, m; void dfs(int u, int f){ for(auto x : edges[u]){ if(x.first == f){ continue; } fa[x.first][0] = u; deep[x.first] = deep[u] + 1; map[x.first] = x.second; dfs(x.first, u); } } void dfs_ans(int u, int f){ for(auto x : edges[u]){ if(x.first == f){ continue; } dfs_ans(x.first, u); cnt[u] += cnt[x.first]; } } void init(){ for(int i = 1; i <= 20; i ++ ){ for(int j = 1; j <= n; j ++ ){ if(fa[j][i - 1]){ fa[j][i] = fa[fa[j][i - 1]][i - 1]; } } } } int lca(int a, int b){ if(deep[a] < deep[b]){ std::swap(a, b); } int t = deep[a] - deep[b]; for(int i = 0; i <= 20; i ++ ){ if((t >> i) & 1){ a = fa[a][i]; } } if(a == b){ return a; } for(int i = 20; i >= 0; i -- ){ if(fa[a][i] != fa[b][i]){ a = fa[a][i]; b = fa[b][i]; } } return fa[a][0]; } void solve(){ std::cin >> n >> m; for(int i = 1; i <= n - 1; i ++ ){ int x, y; std::cin >> x >> y; edges[x].push_back({y, i}); edges[y].push_back({x, i}); } dfs(1, 0); init(); for(int i = 1; i <= m; i ++ ){ int x, y; std::cin >> x >> y; int t = lca(x, y); cnt[x] ++ ; cnt[y] ++ ; cnt[t] -= 2; } dfs_ans(1, 0); int ans = -1; for(int i = n; i >= 1; i -- ){ //std::cout << cnt[i] << " \n"[i == 1]; if(cnt[i] == m){ ans = std::max(ans, map[i]); } } std::cout << ans; } int main(){ #if 0 ios_close; #endif #if 0 freopen(".in", "r", stdin); freopen(".out", "w", stdout); #endif int T = 1; #if 0 std::cin >> T; #endif while(T -- ){ solve(); } return 0; }
- 1