2023蓝桥杯 省赛 C++ B组赛题回顾与赛后反思

A.日期统计

写了一个很长的暴搜,第一题就做了四五十分钟,浪费了很多时间,导致后面没什么时间做了....关键这题最后一对答案还特么错了,艹

B.01串的熵

只需要带入公式计算熵,从小到大枚举\(0\)的数量,直至找到为题目给的熵的 \(0\)的个数.注意精度即可

C.冶炼金属

假设某种金属 A 用了 \(p\) 个炼出了 \(q\) 个新的金属,那么转化率 \(x\) 一定不能大于 \(p\div q\) (转化率都是整数).
那么转化率最大不能超过 \(min\{p_i/q_i\}\).
剩下只需要二分转化率的最小值即可.

(赛时脑子抽了加了个 \(p_i\%q_i==0\)的条件,痛失满分,估计只能拿到一点点分了)

这道题也可以推一下数学式子,本数学蒟蒻就没推出来

点击查看代码
#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.飞机降落

比赛时毫无头绪,交了一发\(n<=2\)的情况骗分就润了.
赛后听群友讲了一下发现只是简单的全排列.....挨个枚举一下飞机的顺序就可以了.痛失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中二分找下标大于等于\(i+k-1\)的b的位置,之后下标比这个大的肯定也能满足.所以个数就是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的最短距离,\(ans = dist [i] + dist[j] - dist[k],k为i和j所经过的公共点\).显然\(k\)\(lca(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分).
刚出考场本来还信心满满的,结果跟群友一讨论发现了诸多错误.后面时间仓促,大题都没有写暴力骗分.最后估摸也就二十多分,心灰意冷,寻思拿到省二就知足了,没想到最后出成绩是省一还是有点出乎我的意料的,运气太好了,感觉这次打的这么烂根本配不上省一....
究其原因还是寒假没好好练代码...回来以后手比较生了.

posted @ 2023-04-27 11:13  LZH_03  阅读(210)  评论(0编辑  收藏  举报