第十四届蓝桥杯省赛C++(B组,持续更新)

冶炼金属

问题描述

小蓝有一个神奇的炉子用于将普通金属O冶炼成为一个特殊金属X

这个炉子有一个称作转换率的属性VV是一个正整数,这意味着消耗V个普通金属O恰好可以冶炼出一个特殊金属X,当普通金属O的数目不足V时,无法继续冶炼。

现在给出了N条冶炼记录,每条记录中包含两个整数AB,这表示本次投入了A个普通金属O,最终冶炼出了B个特殊金属X

每条记录都是独立的,这意味着上一次没消耗完的普通金属O不会累加到下一次的冶炼当中。

根据这N条冶炼记录,请你推测出转换率V的最小值和最大值分别可能是多少,题目保证评测数据不存在无解的情况。

输入格式

第一行一个整数N,表示冶炼记录的数目。

接下来输入N行,每行两个整数AB,含义如题目所述。

输出格式

输出两个整数,分别表示V可能的最小值和最大值,中间用空格分开。

数据范围

对于 30% 的评测用例,1\leq N\leq 10^{2}

对于 60% 的评测用例,1\leq N\leq 10^{3}

对于 100% 的评测用例,1\leq N\leq 10^{4}1\leq B\leq A\leq 10^{9}

输入样例:

3

75 3

53 2

59 2

 输出样例:

20 25

问题分析

由样例数据范围可知,最多接受 O(n^{2}) 的复杂度。

根据题意可知,满足式a=v*b+x,其中 x 为每次冶炼剩余的普通金属。因为能炼出 b 个,而炼不出 (b+1) 个特殊金属,所以满足 v*b\leq a 且 a < v*(b+1),即有\frac{a}{b+1}< v\leq \frac{a}{b}。最小值为\frac{a}{b+1}+1,最大值为\frac{a}{b}

求解代码

#include<cstdio>
#include<iostream>
#include<algorithm>
using namespace std;

const int N = 1e4 + 10;

int n;
int A[N], B[N];

int main()
{
    cin >> n;
    
    for (int i = 0; i < n; i++) scanf("%d%d", &A[i], &B[i]);
    
    // vmax ≥ a / b, vmin < a / (b + 1)
    int vmax = A[0] / B[0], vmin = A[0] / (B[0] + 1) + 1;
    for (int i = 1; i < n; i++)
    {
        int tmax = A[i] / B[i], tmin = A[i] / (B[i] + 1) + 1;
        vmax = min(vmax, tmax);
        vmin = max(vmin, tmin);
    }
    
    cout << vmin << ' ' << vmax << endl;
    
    return 0;
}

飞机降落

问题描述

有 N 架飞机准备降落到某个只有一条跑道的机场。

其中第 i 架飞机在 T_{i} 时刻到达机场上空,到达时它的剩余油料还可以继续盘旋 D_{i} 个单位时间,即它最早可以于 T_{i} 时刻开始降落,最晚可以于 T_{i}+D_{i} 时刻开始降落。

降落过程需要 L_{i} 个单位时间。

一架飞机降落完毕时,另一架飞机可以立即在同一时刻开始降落,但是不能在前一架飞机完成降落前开始降落。

请你判断 N 架飞机是否可以全部安全降落。

输入格式

输入包含多组数据。

第一行包含一个整数 T,代表测试数据的组数。

对于每组数据,第一行包含一个整数 N

以下 N 行,每行包含三个整数:T_{i}D_{i} 和 L_{i}

输出格式

对于每组数据,输出 YES 或者 NO,代表是否可以全部安全降落。

数据范围

对于 30% 的数据,N\leq2

对于 100% 的数据,1\leq T \leq101\leq N \leq 100\leq T_{i},D_{i},L_{i} \leq 10^{5}

输入样例:

2

3

0 100 10

10 10 10

0 2 20

3

0 10 20

10 10 20

20 10 20

输出样例:

YES

NO

问题分析

由题意可知道最多有10架飞机需要安排,属于较为明显的组合优化问题。需要考虑10架飞机的降落顺序,根据数学中排列知识可知方案总数为 N! 个,时间复杂度能够满足条件。

常见的排列数问题可以使用 dfs 进行求解。

求解代码

#include<iostream>
#include<algorithm>
using namespace std;

const int N = 15;

int n, flag;
int T[N], D[N], L[N];
bool st[N];

void dfs(int u, int t)  // u为当前是第几架降落的飞机,t为当前时间
{
    if (u == n) flag = true;
    if (flag) return;
    
    for (int i = 0; i < n; i++)
    {
        if (!st[i])
        {
            if (t > T[i] + D[i]) return;    // 可行性剪枝
            int tt = t;
            st[i] = true;   // 保护现场
            if (t < T[i]) t = T[i];
            t += L[i];
            dfs(u + 1, t);
            st[i] = false;  // 恢复现场
            t = tt;
        }
    }
}

int main()
{
    int t;
    cin >> t;
    
    while (t--)
    {
        cin >> n;
        for (int i = 0; i < n; i++) cin >> T[i] >> D[i] >> L[i];
        
        flag = false;
        dfs(0, 0);
        
        if (flag) puts("YES");
        else puts("NO");
    }
    
    return 0;
}

岛屿个数

问题描述

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

在岛屿 A 所占据的格子中,如果可以从中选出 k 个不同的格子,使得他们的坐标能够组成一个这样的排列:(x_{0},y_{0}),(x_{1},y_{1}),\dots,(x_{k-1},y_{k-1})),其中 (x_{(i+1)\%k},y_{(i+1)\%k}) 是由 (x_{i},y_{i}) 通过上/下/左/右移动一次得来的 (0\leq i\leq k - 1),此时这 k 个格子就构成了一个 “环”。

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

若 B 是 A 的子岛屿,C 又是 B 的子岛屿,那 C 也是 A 的子岛屿。

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

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

输入格式

第一行一个整数 T,表示有 T 组测试数据。

接下来输入 T 组数据。

对于每组数据,第一行包含两个用空格分隔的整数 MN 表示地图大小;接下来输入 M 行,每行包含 N 个字符,字符只可能是 0 或 1

输出格式

对于每组数据,输出一行,包含一个整数表示答案。

数据范围

对于 30% 的评测用例,1\leq M,N\leq10
对于 100% 的评测用例,1\leq T \leq10,1\leq M,N\leq 50

输入样例:

2

5 5

01111

11001

10101

10001

11111

5 6

111111

100001

010101

100001

111111

输出样例:

1

3

样例解释

对于第一组数据,包含两个岛屿,下面用不同的数字进行了区分:

01111

11001

10201

10001

11111

岛屿 2 在岛屿 1 的 “环” 内部,所以岛屿 2 是岛屿 1 的子岛屿,答案为 1。

对于第二组数据,包含三个岛屿,下面用不同的数字进行了区分:

111111

100001

020301

100001

111111

注意岛屿 3 并不是岛屿 1 或者岛屿 2 的子岛屿,因为岛屿 1 和岛屿 2 中均没有“环”。

问题分析

经典的 bfs 问题,最多仅需遍历整张地图,故时间复杂度为O(MN)

利用 g 数组来存储地图,在读入的过程中需使用到字符串处理,st 数组来记录是否被访问过。

问题描述中写的较为复杂难懂,实际表达的意思即为海水朝周围8个方向进行搜索,而陆地朝周围4个方向进行搜索。

在读入地图时,从 g[1][1] 开始读入,将 g 数组的外围视为外海,此时遍历的第一个节点一定为海水。

(此题没有过多思考,按照模板来书写)

求解代码

#include<iostream>
#include<cstring>
#include<queue>
#include<algorithm>
#define x first
#define y second
using namespace std;

typedef pair<int, int> PII;

const int N = 60;

int m, n;
int g[N][N];
bool st[N][N];

void bfs_1(int sx, int sy)
{
    queue<PII> q;
    q.push({sx, sy});
    st[sx][sy] = true;

    while(q.size())
    {
        PII t = q.front();
        q.pop();
        int dx[4] = {-1, 0, 1, 0}, dy[4] = {0, -1, 0, 1};
        for (int i = 0; i < 4; i++)
        {
            int nx = t.x + dx[i], ny = t.y + dy[i];
            if (nx < 0 || nx > m + 1 || ny < 0 || ny > n + 1) continue;    // 出界
            if (st[nx][ny]) continue;  // 已被访问
            if (g[nx][ny] == 0) continue;
            // 入队并标记
            q.push({nx, ny});
            st[nx][ny] = true;
        }
    }

    return;
}

void bfs_0(int sx, int sy, int &res)
{
    queue<PII> q;
    q.push({sx, sy});
    st[sx][sy] = true;

    while (q.size())
    {
        // 取队首
        PII t = q.front();
        q.pop();
        // 后继节点入队
        int dx[8] = {0, 1, 1, 1, 0, -1, -1, -1}, dy[8] = {-1, -1, 0, 1, 1, 1, 0, -1};
        for (int i = 0; i < 8; i++)
        {
            int nx = t.x + dx[i], ny = t.y + dy[i];
            if (nx < 0 || nx > m + 1 || ny < 0 || ny > n + 1) continue;    // 出界
            if (st[nx][ny]) continue;  // 已被访问
            if (g[nx][ny] == 1) 
            {
                bfs_1(nx, ny), res++;
                continue;
            }
            // 入队并标记
            q.push({nx, ny});
            st[nx][ny] = true;
        }
    }

    return;
}

int main()
{
    int T;
    cin >> T;
    while (T--)
    {
        cin >> m >> n;
        // 重置地图和状态
        memset(g, 0, sizeof g);
        memset(st, false, sizeof st);
        // 读入地图
        for (int i = 1; i <= m; i++)
            for (int j = 1; j <= n; j++)
            {
                char c; cin >> c;
                g[i][j] = c - '0';
            }
        // bfs
        int res = 0;
        bfs_0(0, 0, res);

        cout << res << endl;
    }
    return 0;
}

子串简写

问题描述

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

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

例如 internationalization 简写成 i18n, Kubernetes 简写成 K8s,Lanqiao 简写成 L5o。

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

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

输入格式

第一行包含一个整数 K

第二行包含一个字符串 S 和两个字符 c_{1} 和 c_{2}

输出格式

一个整数代表答案。

数据范围

对于 20% 的数据,2\leq K \leq|S| \leq10000

对于 100% 的数据,2\leq K \leq |S| \leq 5*10^{5}。 S 只包含小写字母。 c_{1} 和 c_{2} 都是小写字母。

|S| 代表字符串 S 的长度。

输入样例:

4

abababdb a b

输出样例:

6

问题分析

由数据样例范围可知,时间复杂度不能超过 O(nlogn)

首先,容易想到最朴素(暴力)的做法:两重for循环判断所有子串,复杂度达到了 O(n^{2}) 必然是会TLE的。当然,在比赛的时候也能通过一些小样例拿到一定的分数。

接下来,需要思考如何对朴素做法进行优化。这里使用双指针动态维护一段长度为 k 的区间,在右端点往后用前缀和思想预处理 c_{2} 的数量。从而将时间复杂度降低到 O(n) 。

求解代码

#include<iostream>
#include<cstring>
using namespace std;

const int N = 50010;

int k;
char c1, c2;
string s;

int main()
{
    cin >> k >> s >> c1 >> c2;
    
    long long cnt = 0, res = 0;
    
    for (int i = 0, j = k - 1; s[j]; i++, j++)
    {
        if (s[i] == c1) cnt++;
        if (s[j] == c2) res += cnt;
    }
    
    cout << res << endl;

    return 0;
}

  • 46
    点赞
  • 29
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值