2023蓝桥杯 省赛 C++ B组赛题回顾与赛后反思
A.日期统计
写了一个很长的暴搜,第一题就做了四五十分钟,浪费了很多时间,导致后面没什么时间做了....关键这题最后一对答案还特么错了,艹
B.01串的熵
只需要带入公式计算熵,从小到大枚举
C.冶炼金属
假设某种金属 A 用了
那么转化率最大不能超过
剩下只需要二分转化率的最小值即可.
(赛时脑子抽了加了个
这道题也可以推一下数学式子,本数学蒟蒻就没推出来
点击查看代码
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<iostream>
#include<algorithm>
typedef long long LL;
using namespace std;
#define int long long
const int N = 1e6+10;
int a[N],b[N];
int n;
int check(int x)
{
for(int i=0;i<n;i++)
{
if(a[i]/x>b[i])return 0;
}
return 1;
}
signed main()
{
scanf("%lld",&n);
int rr;
int l = 1,r = 2147483647;
for(int i=0;i<n;i++)
{
scanf("%lld%lld",&a[i],&b[i]);
r = min(r,a[i]/b[i]);
}
rr = r;
while(l<r)
{
int mid = (l+r)>>1;
if(check(mid))
{
r = mid;
}
else
{
l = mid+1;
}
}
printf("%lld %lld",l,rr);
return 0;
}
D.飞机降落
比赛时毫无头绪,交了一发
赛后听群友讲了一下发现只是简单的全排列.....挨个枚举一下飞机的顺序就可以了.痛失10分
点击查看代码
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<iostream>
#include<algorithm>
typedef long long LL;
using namespace std;
const int N = 1e6+10;
int t[N],d[N],l[N];
int p[100];
int vis[100];
int solve(int n,int step)
{
if(step==n)
{
int start = 0;
for(int i=0;i<n;i++)
{
int now = p[i];
if(start>t[now]+d[now])
{
return 0;
}
else
{
start = max(start,t[now]);
start+=l[now];
}
}
return 1;
}
for(int i=0;i<n;i++)
{
if(!vis[i])
{
vis[i] = 1;
p[step] = i;
if(solve(n,step+1))
return 1;
vis[i] = 0;
}
}
return 0;
}
int main()
{
int T;
scanf("%d",&T);
while(T--)
{
int n;
scanf("%d",&n);
for(int i=0;i<n;i++)
scanf("%d%d%d",&t[i],&d[i],&l[i]);
memset(vis,0,sizeof vis);
if(solve(n,0))
{
printf("YES\n");
}
else printf("NO\n");
}
return 0;
}
E.接龙数列
比赛前只看了一点背包DP,线性DP根本没看,本来以为要寄,还好幸运女神眷顾了一下我,凭借残存的记忆想到了之前做过的最长上升子序列的方法,两层循环肯定是会超时的,类比于最长上升子序列的优化方法,记录一下每种字母的末尾最长长度的下标即可.这样就不需要回溯遍历之前每个单词了.
这题我算是有一定把握的,然后写了个对拍测了十几分钟发现没什么问题就交了.(一个寒假没怎么做题,对拍不会写了浪费了不少时间)
还好赛后在某网站提交自测了一发过了.不然估计要省四了,全程一共没对几道题.
点击查看代码
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<iostream>
#include<algorithm>
typedef long long LL;
using namespace std;
const int N = 1e6 + 10;
int a[N];
int dp[N];
int maxlen[10];
int maxid[10];
int getfirst(int x)
{
int t = x;
while (x)
{
t = x % 10;
x /= 10;
}
return t;
}
int main()
{
int n;
scanf("%d", &n);
for (int i = 0; i < n; i++)
{
scanf("%d", &a[i]);
}
for (int i = 0; i < n; i++)
{
dp[i] = 1;
int t = getfirst(a[i]);
int last = a[i] % 10;
dp[i] = max(dp[i], maxlen[t] + 1);
if (dp[i] >= maxlen[last])
{
maxlen[last] = dp[i];
maxid[last] = i;
}
}
int m = dp[n];
for (int i = 0; i < n; i++)
{
m = max(m, dp[i]);
}
printf("%d", n - m);
return 0;
}
F.岛屿个数
G.子串简写
记要找的子串的左边界为a,右边界为b
先扫一遍字符串,记录子串中所有b的位置下标存到数组c里,cnt+1.
然后遍历字符串,如果第i个字符为a,就在数组c中二分找下标大于等于cnt - (lower_bound(t,t+cnt,i+k-1)-t)
考试的时候一点头绪没有,交了一发暴力骗分.
赛后补题的时候想了一会儿就想出来了...猜测可能当时做题时间不太够了没有沉下心来认真想一想.
方法二:
用前缀和记录前i个位置下的b的个数. 链接
点击查看代码
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<iostream>
#include<algorithm>
#define pb push_back
typedef long long LL;
using namespace std;
const int N = 1e6+10;
const int debug =0;
int t[N];
int cnt =0;
int main()
{
int k;
scanf("%d",&k);
string s;
char c1[2],c2[2];
cin>>s;
scanf("%s%s",c1,c2);
char a = *c1,b= *c2;
for(int i=0;s[i];i++)
{
if(s[i]==b)
t[cnt++]= i;
if(debug)
printf("t[%d] = %d\n",cnt-1,t[cnt-1]);
}
LL res=0;
for(int i=0;s[i];i++)
{
if(s[i]==a)
{
res+= cnt - (lower_bound(t,t+cnt,i+k-1)-t);
if(debug)
printf("%lld\n",res);
}
}
printf("%lld",res);
return 0;
}
H.整数删除
比赛时这题也并没做出来,没找到什么技巧,即便是个模拟题,剩的时间也不多了.于是这题直接跳过了.
赛后补题时也没啥思路.经大佬指导,可以用一个堆priority_queue<PII ,vector<PII>,greater<PII> >q;
(堆默认是大顶堆,因此加个greater.)来维护最小值.堆的元素是pair<int,int>
,第一个为元素值,第二个为该元素在数组内的下标.
由于需要频繁地进行删除操作,因此单纯选用数组肯定太慢了.如果用链表,则不方便取最小值.
所以将两者统一,选用数组来表示链表比较合适.
删除元素时,只需要更新l和r数组就可以了.
还有一个问题,删除元素后,其相邻元素的值是会发生改变的.但是我们早就把这些元素存到堆里面了,这时候如果再去堆里的值,可能就取到未加数的元素了,这该怎么解决呢?
佬用了一个很巧妙的方法,直接把加数后的新元素push到堆内,然后更新数组a的值(这是容易做到的),取堆顶元素时,直接判断取出来的这个数的值(first)是不是等于这个数的下标(second)的a数组的值就可以了.如果不等于,说明这是一个旧值,直接pop掉.反之可以继续做.
注意我们向堆中额外的添加了元素,那么我们循环跳出的条件也要注意改一下,不能看size()了.而是用一个cnt记录删除的次数,达到k则跳出循环.
最后从r[0] 按序输出各元素值即可.
回头来看这道题难度其实并不高,代码如下
点击查看代码
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<iostream>
#include<algorithm>
#include<queue>
#define pb push_back
#define int long long
typedef long long LL;
using namespace std;
const int N = 1e6 + 10;
int a[N];
int l[N], r[N];
typedef pair<int, int > PII;
priority_queue<PII ,vector<PII>,greater<PII> > q;
signed main()
{
int n, k;
scanf("%lld%lld", &n, &k);
for(int i = 1; i <= n; i++)
{
scanf("%lld", &a[i]);
l[i] = i - 1;
r[i] = i + 1;
q.push({a[i], i});
}
r[0] = 1;
int cnt = 0;
while(cnt < k)
{
PII t = q.top();
q.pop();
if(a[t.second] != t.first)
{
continue;
}
int id = t.second;
int val = t.first;
//printf("%d %d\n",id,val);
a[l[id]] += val;
a[r[id]] += val;
if(l[id])
q.push({a[l[id]], l[id]});
if(r[id]<=n)
q.push({a[r[id]], r[id]});
l[r[id]] = l[id];
r[l[id]] = r[id];
//a[id] = 0;
cnt++;
//printf("%d\n",r[0]);
// for(int i = r[0]; i <= n; i = r[i])
// {
// printf("%d ", a[i]);
// }
// printf("\n");
}
for(int i = r[0]; i <= n; i = r[i])
{
printf("%lld ", a[i]);
}
return 0;
}
I.景区导游
赛时也是没有做出来这道题 (我怎么这么菜啊!!!)
赛时想了一个假做法.后来没时间了反正也没写完,不亏()
这题可以这么想,先找一个点作为原点,跑一个dijkstra , 然后对于i到j的最短距离,
正解:直接套用lca模板即可.求前缀和,连dijkstra都不用跑.
点击查看代码
//倍增法
#include <cstdio>
#include <cstring>
#include <iostream>
#include <vector>
#define int long long
#define MXN N
const int N = 1e5 + 10;
using namespace std;
std::vector<int> v[MXN];
std::vector<int> w[MXN];
int fa[MXN][31], cost[MXN][31], dep[MXN];//从i号结点向上2^(k-1)层的结点的编号. 从i号结点向上2^(k-1)层的花费. i号结点的深度
int n, m;
int a, b, c;
// dfs,用来为 lca 算法做准备。接受两个参数:dfs 起始节点和它的父亲节点。
void dfs(int root, int fno)
{
// 初始化:第 2^0 = 1 个祖先就是它的父亲节点,dep 也比父亲节点多 1。
fa[root][0] = fno;
dep[root] = dep[fa[root][0]] + 1;
// 初始化:其他的祖先节点:第 2^i 的祖先节点是第 2^(i-1) 的祖先节点的第
//向上 2^(i-1) 层的祖先节点。
for (int i = 1; i < 31; ++i)
{
fa[root][i] = fa[fa[root][i - 1]][i - 1];
cost[root][i] = cost[fa[root][i - 1]][i - 1] + cost[root][i - 1];
}
// 遍历子节点来进行 dfs。
int sz = v[root].size();
for (int i = 0; i < sz; ++i)
{
if (v[root][i] == fno) continue;
cost[v[root][i]][0] = w[root][i];
dfs(v[root][i], root);
}
}
// lca。用倍增算法算取 x 和 y 的 lca 节点。先跳到同一深度,再一起向上跳.
int lca(int x, int y)//返回值是跳到祖先的花费
{
if(x==0||x==n+1||y==0||y==n+1)return 0;
// 令 y 比 x 深。
if (dep[x] > dep[y]) swap(x, y);
// 令 y 和 x 在一个深度。
int tmp = dep[y] - dep[x], ans = 0;
for (int j = 0; tmp; ++j, tmp >>= 1)
if (tmp & 1) ans += cost[y][j], y = fa[y][j];
// 如果这个时候 y = x,那么 x,y 就都是它们自己的祖先。
if (y == x) return ans;
// 不然的话,找到第一个不是它们祖先的两个点。为什么不直接跳到是它们祖先的点
//因为可能会跳过了,可能跳到他们共同祖先的祖先.具体可以回顾视频www.bilibili.com/video/BV1N7411G7JD
for (int j = 30; j >= 0 && y != x; --j)//注意是从最大的步数开始跳,也就是跳的尽可能向根靠近
{
if (fa[x][j] != fa[y][j])
{
ans += cost[x][j] + cost[y][j];
x = fa[x][j];
y = fa[y][j];
}
}
// 返回结果。
ans += cost[x][0] + cost[y][0];
return ans;
}
int q[N];
int pre[N];
int final[N];
signed main()
{
// 初始化表示祖先的数组 fa,代价 cost 和深度 dep。
memset(fa, 0, sizeof(fa));
memset(cost, 0, sizeof(cost));
memset(dep, 0, sizeof(dep));
// 读入树:节点数一共有 n 个。
scanf("%lld", &n);
int k;
scanf("%lld", &k);
for (int i = 1; i < n; ++i)
{
scanf("%lld %lld %lld", &a, &b, &c);
//++a, ++b;
v[a].push_back(b);
v[b].push_back(a);
w[a].push_back(c);
w[b].push_back(c);
}
// 为了计算 lca 而使用 dfs。
dfs(1, 0);
for(int i = 1; i <= k; i++)
{
scanf("%lld", &q[i]);
}
for(int i = k; i >= 1; i--)
{
final[i] = final[i + 1] + lca(q[i + 1], q[i]);
//printf("%d\n",final[i]);
}
for(int i = 1; i <= k; i++)
{
pre[i] = pre[i - 1] + lca(q[i - 1], q[i]);
}
for(int i = 1; i <= k; i++)
{
printf("%lld ", pre[i - 1] + lca(q[i - 1], q[i + 1]) + final[i + 1]);
}
return 0;
}
J.
总结
回顾下来全场一共就一道填空和一道编程拿了满分(一共20分).
刚出考场本来还信心满满的,结果跟群友一讨论发现了诸多错误.后面时间仓促,大题都没有写暴力骗分.最后估摸也就二十多分,心灰意冷,寻思拿到省二就知足了,没想到最后出成绩是省一还是有点出乎我的意料的,运气太好了,感觉这次打的这么烂根本配不上省一....
究其原因还是寒假没好好练代码...回来以后手比较生了.
如果您觉得阅读本文对您有帮助,请点一下“推荐”按钮,您的“推荐”将是我最大的写作动力!欢迎各位转载,但是未经作者本人同意,转载文章之后必须在文章页面明显位置给出作者和原文连接,否则保留追究法律责任的权利。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· Manus爆火,是硬核还是营销?
· 终于写完轮子一部分:tcp代理 了,记录一下
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通