第十四届蓝桥杯省赛C++B组题目及解析(二)

此篇接续上一篇之后的题目,为4-6题,如果需要前三题的解析请看上一篇博客(第十四届蓝桥杯省赛C++B组题目及解析-CSDN博客

第四题题目链接:4959. 岛屿个数 - AcWing题库

小蓝得到了一副大小为 M×N的格子地图,可以将其视作一个只包含字符 0(代表海水)和 1(代表陆地)的二维数组,地图之外可以视作全部是海水,每个岛屿由在上/下/左/右四个方向上相邻的 1 相连接而形成。

在岛屿 A所占据的格子中,如果可以从中选出 k 个不同的格子,使得他们的坐标能够组成一个这样的排列:(x0,y0),(x1,y1),...,(xk−1,yk−1),其中 (x(i+1)%k,y(i+1)%k) 是由 (xi,yi) 通过上/下/左/右移动一次得来的 (0≤i≤k−1),此时这 k个格子就构成了一个 “环”。

如果另一个岛屿 B所占据的格子全部位于这个 “环” 内部,此时我们将岛屿 B 视作是岛屿 A的子岛屿。若 B是 A 的子岛屿,C 又是 B 的子岛屿,那 C 也是 A的子岛屿。

请问这个地图上共有多少个岛屿?

在进行统计时不需要统计子岛屿的数目。

在处理这种地图题目,有一种惯用的套路就是将数据中的地图外围再扩大一圈,扩大的作用不仅可以用来更好的进行后续的数据处理,还能防止在进行dfs或bfs在处理到边界点由于越界而产生奇怪的错误。本题中同样可以利用扩大一圈的方法将周围用一圈海水进行填充。

分析题目给的两个样例,样例一中存在子岛的情况,子岛在计算岛屿的数量的过程中是不算的,因此需要考虑如何判断一个岛是不是子岛。如果一个岛被另一个环形的岛屿完全包围了那么就成为了一个子岛,而是否完全包围可以通过是否可以和拓展后的最外面一圈的海水相接,如第二个样例中的内部的两个陆地能够通过最左边一列的中间的那个‘0’和外界的海洋进行相接,因此不是子岛是独立的岛屿。

因此该题目可以这样考虑:

首先对扩展后的最外圈的海水进行遍历,凡是与遍历到的海水的方块相邻的陆地方块相邻的陆地方块再进行遍历。因此本题需要两个dfs函数,一个用于遍历最外圈的海水,一个用于陆地。代码如下:

#include <iostream>
#include <cstring>
#include <cstdio>
#include <algorithm>
using namespace std;
typedef long long LL;

const int N = 55;
int map[N][N];
int t;
int n, m, ans;
int st[N][N];
int dx[4] = {0, 0, 1, -1};

int dy[4] = {1, -1, 0, 0};

void dfs1(int a, int b) {
	st[a][b] = 1;

	for (int i = 0; i < 4; i ++) {

		int x = dx[i] + a;
		int y = dy[i] + b;

		if (st[x][y] || map[x][y] == 0)
			continue;
		dfs1(x, y);
	}
}

void dfs2(int a, int b) {
	st[a][b] = 1;

	for (int i = -1; i <= 1; i ++) {

		for (int j = -1; j <= 1; j ++) {

			int x = i + a;
			int y = j + b;

			if (x < 0 || x > n + 1 || y < 0 || y > m + 1 || st[x][y])
				continue;

			if (map[x][y] == 0)
				dfs2(x, y);
			else {
				dfs1(x, y);
				ans ++;
			}
		}
	}
}

int main() {
	ios::sync_with_stdio(false);
	cin >> t;

	while (t --) {
		cin >> n >> m;
		memset(map, 0, sizeof map);
		memset(st, 0, sizeof st);
		ans = 0;

		for (int i = 1; i <= n; i ++)

			for (int j = 1; j <= m; j ++) {
				char c;
				cin >> c;
				map[i][j] = c - '0';
			}

		dfs2(0, 0);
		cout << ans << endl;
	}

	return 0;
}

其中dfs1是用于陆地的遍历,通过四个方向判断是否存在没有标记过的和本方块连通的陆地进行遍历,dfs2是用于最外围海洋的遍历,通过八个方向判断是否存在没有标记过的陆地,如果存在则跳转到dfs1进行遍历,如果没有则继续对海洋方块进行dfs2遍历直到搜索完最外围海洋中的所有的方块。(海水连通性需要通过8个方向判断,而陆地的连通性是通过四个方向判断),代码成功通过了所有的测试用例。

第五题题目链接:4960. 子串简写 - AcWing题库

程序猿圈子里正在流行一种很新的简写方法:

对于一个字符串,只保留首尾字符,将首尾字符之间的所有字符用这部分的长度代替。

例如 internationalization 简写成 i18nKubernetes 简写成 K8sLanqiao 简写成 L5o 等。

在本题中,我们规定长度大于等于 K的字符串都可以采用这种简写方法(长度小于 K的字符串不配使用这种简写)。

给定一个字符串 S和两个字符 c1 和 c2,请你计算 S 有多少个以 c1 开头 c2 结尾的子串可以采用这种简写?

根据数据范围可以得知这道题目应该有时间复杂度较小的做法,用一个数组s[x]表示在第x个字符前c1出现的次数,那么当字符串中第y个位置出现c2的时候,符合条件的字符串有s[y - k + 1]个,通过对字符串进行一次遍历即可得到答案。代码如下:

#include <iostream>
#include <cstring>
#include <cstdio>
#include <algorithm>
using namespace std;
typedef long long LL;

const int N = 500010;
int k, t;
string s1;
char c1, c2;
int s[N];
LL ans;

int main() {
	ios::sync_with_stdio(false);
	cin >> k >> s1 >> c1 >> c2;
	s1 = " " + s1;

	for (int i = 1; i < s1.length() ; i ++) {

		if (s1[i] == c1)
			t ++;
		s[i] = t;

		if (i >= k && s1[i] == c2)
			ans += s[i - k + 1];
	}

	cout << ans << endl;
	return 0;
}

采用前缀和的思思路,将用一个前缀和数组s来存储c1字符出现的次数,当出现c2的时候就可以通过s数组来计算以该字符结尾中符合条件的子串的数目。

第六题题目链接:4961. 整数删除 - AcWing题库

给定一个长度为 N 的整数数列:A1,A2,...,AN。

你要重复以下操作 K次:

每次选择数列中最小的整数(如果最小值不止一个,选择最靠前的),将其删除,并把与它相邻的整数加上被删除的数值。

输出 K次操作后的序列。

看完题目,需要一个能够实现从小到大排序和对这个数组前驱和后继都进行操作的数据结构,符合前一个要求的是堆或者优先队列,符合后一个要求的是双向链表,因此本题可以使用优先队列加双向链表或者堆加双向链表。

采用STL库中的优先队列能够使代码更简洁,因此采用优先队列实现从小到大取数的操作。采用一维数组模拟双链表进行前驱和后继的修改操作。

#include <bits/stdc++.h>
using namespace std;
typedef long long LL;


const int N = 500010;
int n, k;
LL v[N], l[N], r[N];
priority_queue<pair<LL, int>, vector<pair<LL, int>>, greater<pair<LL, int>>> h;

void del(int x) {
	r[l[x]] = r[x];
	l[r[x]] = l[x];
	v[l[x]] += v[x];
	v[r[x]] += v[x];
}

int main() {
	ios::sync_with_stdio(false);
	cin >> n >> k;

	for (int i = 1; i <= n; i ++) {

		cin >> v[i];
		l[i] = i - 1;
		r[i] = i + 1;
		h.push({v[i], i});
	}

	while (k --) {
		auto p = h.top();
		h.pop();

		if (p.first != v[p.second]) {
			h.push({v[p.second], p.second});
			k ++;
		} else
			del(p.second);
	}

	int a = r[0];

	while (a != n + 1) {
		cout << v[a] << " ";
		a = r[a];
	}

	return 0;
}

运行后喜提一个Output Limit Exceeded,本来以为是写的过程有问题,看了一下通过情况应该是加强后的数据太强了会卡这种用优先队列的写法,如果是正常的比赛当中的数据应该是不会这样出现故意卡优先队列写法的情况。

后续会对最后一道题目中出现的手搓双链表部分进行更新,未完待续......

  • 21
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值