2023第十四届蓝桥杯省赛
A.冶炼金属
解题思路
对于每组来说, 最大值肯定是用a/b得到, 而最小值则是用a/(b-1)+1来获取; 最终答案则是要最大值中的最小值和最小值中的最大值;
神秘代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
typedef pair<int, int> PII;
const int N = 1e4 + 10;
int n, m, k;
signed main() {
cin >> n;
int minn = 0;
int maxn = 0x3f3f3f3f;
for (int i = 1; i <= n; i++) {
int a, b;
cin >> a >> b;
int x = a / b;
int y = a / (b + 1) + 1;
maxn = min(maxn, x);
minn = max(minn, y);
}
cout << minn << ' ' << maxn;
return 0;
}
B.飞机降落
解题思路
本题可以用状态压缩dp来求解, 这题我当时是直接暴力了, 状压dp还是太不熟练了; 本题主要是因为n<10; 所以可以想到用状压dp; f[i]则表示当降落情况为i时所需要的最短时间; 状态计算 f[i] = min(f[i], max(f[i-(1<<j)], t) + l); 注意如果状态计算时的max, 如果没到到达时间就得补上;
神秘代码
#include<bits/stdc++.h>
//#define int long long
using namespace std;
typedef pair<int, int> PII;
const int N = 1e4 + 10;
int n, m, t;
int f[N];
struct {
int a, b, c;
}air [N];
signed main() {
cin >> t;
while (t--) {
cin >> n;
for (int i = 1; i <= n; i++) {
int a, b, c;
cin >> a >> b >> c;
air[i] = { a,b,c };
}
memset(f, 0x3f, sizeof f);
f[0] = 0;
for (int i = 1; i < 1 << n; i++) {
for (int j = 0; j < n; j++) {
int a = air[j + 1].a, b = air[j + 1].b, c = air[j + 1].c;
if (i >> j & 1) {
int x = f[i - (1 << j)];
if (x <= a + b) {
f[i] = min(f[i], max(x, a) + c);
}
}
}
}
if (f[(1 << n) - 1] < 0x3f3f3f3f) cout << "YES" << endl;
else cout << "NO" << endl;
}
return 0;
}
C.接龙数列
解题思路
还是一个比较明显的dp问题; 状态表示 f(i,j) 表示前i个数中以j结尾的数列的最大长度; 状态计算分为选和不选两个状态; 对于当前这个数, 我们可以先把不选的最大长度算出来, 因为不选当前这个, 所以我们遍历0~9作为结尾: f[i][j] = f[i - 1][j]; 如果选择当前这个数, 那我们就得选择以这个数开头为结尾的状态, 设a为开头, b为结尾: f[i][b] = max(f[i][b], f[i - 1][a]+1)
对此我们还有一个空间优化版本, 对于上面状态计算中不选当前数的情况, 我们的操作实际上就是把前面以b结尾的状态延续到了第i个数; 比如数列中第2个数是以4结尾的, 从第3个数就断开了, 如果到了第5个数是以4开头的, 那么我们计算f[6][4]就需要用到f[5][4], 而其中f[5][4], f[4][4], f[3][4], 都需要从f[2][4]转移而来; 所以才需要做f[i][j] = f[i - 1][j]的计算; 而空间优化版我们可以把前面的i优化掉, 只保留f[j]; 这样我们的状态计算就变成了f[b] = max(f[b], f[a] + 1); 其中f[b]就是前面最后一个出现的以b结尾的数列的最大长度, 这样就省去了继承的过程
神秘代码
//朴素版本;
#include<bits/stdc++.h>
#define int long long
using namespace std;
typedef pair<int, int> PII;
const int N = 1e5 + 10;
int n, m, t;
int f[N][10];
signed main() {
cin >> n;
for (int i = 1; i <= n; i++) {
string s;
cin>>s;
int a = s.front() - '0';
int b = s.back() - '0';
for (int j = 0; j <= 9; j++) {
f[i][j] = f[i - 1][j];
}
f[i][b] = max(f[i][b], f[i - 1][a] + 1);
}
int maxn = 0;
for (int i = 0; i <= 9; i++) maxn = max(maxn, f[n][i]);
cout << n - maxn;
return 0;
}
//空间优化版本
#include<bits/stdc++.h>
#define int long long
using namespace std;
typedef pair<int, int> PII;
const int N = 1e5 + 10;
int n, m, t;
int f[10];
signed main() {
cin >> n;
for (int i = 1; i <= n; i++) {
string s;
cin >> s;
int a = s.front() - '0';
int b = s.back() - '0';
f[b] = max(f[b], f[a] + 1);
}
int maxn = 0;
for (int i = 0; i <= 9; i++) maxn = max(maxn, f[i]);
cout << n - maxn;
return 0;
}
D.岛屿个数
解题思路
本题的难点在于怎么判断外岛和子岛; 如果我们发现一个岛已经形成了环, 那我们就不用去看这个环内部的情况了; 对此我们需要从整个图的最外层入手, 为了防止有的岛就在边缘, 我们可以再给图增加一圈外海; 然后我们可以从外海开始进行bfs, 只要能连接到外海, 就一定不是子岛; 从第二个样例我们发现海水是8向流通, 而陆地是4向流通, 所以我们要用两个bfs来做; 如果外海在蔓延过程中遇到了岛屿, 那么就数量+1并对该岛屿进行bfs, 凡是蔓延过的岛屿或海水都要做个标记, 防止重复;
神秘代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
typedef pair<int, int> PII;
const int N = 60;
int n, m, t, idx;
int g[N][N];
bool st[N][N];
int dx[] = { 1,0,-1,0 ,1,1,-1,-1 }, dy[] = { 0,1,0,-1 ,1,-1,1,-1 };
void bfs2(int a,int b) {
st[a][b] = true;
for (int i = 0; i < 4; i++) {
int x = a + dx[i], y = b + dy[i];
if (x >= 1 && x <= n && y >= 1 && y <= m && !st[x][y] && g[x][y] == 1) {
bfs2(x, y);
}
}
}
void bfs1(int a,int b) {
st[a][b] = true;
for (int i = 0; i < 8; i++) {
int x = a + dx[i], y = b + dy[i];
if (x >= 0 && x <= n + 1 && y >= 0 && y <= m + 1&&!st[x][y]) {
if (g[x][y] == 1) {
bfs2(x, y);
idx++;
}
else bfs1(x, y);
}
}
}
signed main() {
cin >> t;
while (t--) {
idx = 0;
memset(st, false, sizeof st);
memset(g,0,sizeof g);
cin >> n >> m;
for (int i = 1; i <= n; i++) {
string s;
cin >> s;
for (int j = 0; j < s.size(); j++) {
g[i][j + 1] = s[j] - '0';
}
}
bfs1(0,0);
cout << idx<<endl;
}
return 0;
}
E.子串简写
解题思路
因为字符串的长度是1e5级别, 所以我们只能线性的遍历字符串, 所以很容易想到利用双指针求解, 设子串以a开头, 以b结尾;
注意两点即可: 一是i和j之间距离要大于等于n; 二是假设i第一次到达b时, 这时发现有2个j满足要求, 此时结果为2, 当i到达第二个b时, 有1个j满足要求, 此时结果不仅要+1, 还要把之前的那2个也加上, 故结果是3; 所以我们可以开两个变量idx和res, idx表示当前一共有多少个j满足要求, 而res代表结果, 每次j走完后让res加上idx即可;
神秘代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
typedef pair<int, int> PII;
const int N = 1e6 + 10;
int n, m,idx,res;
int f[N];
signed main() {
cin >> n;
string s1, s = " ";
cin >> s1;
s += s1;
char a, b;
cin >> a >> b;
for (int i = 1, j = 1; i < s.size(); i++) {
if (s[i] == b) {
while (j <= i - n + 1) {
if (s[j] == a) idx++;
j++;
}
res+=idx;
}
}
cout << res;
return 0;
}
F.整数删除
解题思路
因为要快速得到最小值, 所以可以想到用堆排序; 又因为要快速定位删除并更新其左右的值, 所以可以用双向链表进行维护; 优先队列的元素要用pair类型来储存值和位置; 当进行操作时, 难点在于怎么更新优先队列里的值; 我们发现每次操作都会让值增大, 所以当我们取堆顶的值时, 对照一下看看当前值和链表里面对应位置的值是否相同; 如果不同, 则把原值删除后把链表里的值和位置插入到优先队列里, 这样就实现了优先队列里值的更新;
注意别忘了初始化re[0]和le[n+1];
神秘代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
typedef pair<int, int> PII;
const int N = 1e6 + 10;
int n, m,idx;
int le[N], re[N],e[N];
signed main() {
priority_queue<PII, vector<PII>, greater<PII>> q;
cin >> n >> m;
re[0]=1,le[n+1]=n;
for (int i = 1; i <= n; i++) {
cin >> e[i];
q.push({e[i],i});
le[i] = i - 1;
re[i] = i + 1;
}
while (m--) {
auto t = q.top();
q.pop();
int x = t.first, y = t.second;
if (x != e[y]) {
m++;
q.push({e[y],y});
continue;
}
e[re[y]] += x;
e[le[y]] += x;
le[re[y]] = le[y];
re[le[y]] = re[y];
}
int i = re[0];
while (i < n + 1) {
cout << e[i] << ' ';
i = re[i];
}
return 0;
}