You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

4.0 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

AcWing 422. 校门外的树

一、题目描述

某校大门外长度为 L 的马路上有一排树,每两棵相邻的树之间的间隔都是 1 米。

我们可以把马路看成一个数轴,马路的一端在数轴 0 的位置,另一端在 L 的位置;数轴上的每个整数点,即 012,……,L,都种有一棵树。

由于马路上有一些区域要用来建地铁。

这些区域用它们在数轴上的起始点和终止点表示。

已知任一区域的起始点和终止点的坐标都是整数,区域之间可能有重合的部分。

现在要把这些区域中的树(包括区域端点处的两棵树)移走。

你的任务是计算将这些树都移走后,马路上还有多少棵树。

输入格式 输入文件的第一行有两个整数 LML 代表马路的长度,M 代表区域的数目,LM 之间用一个空格隔开。

接下来的 M 行每行包含两个不同的整数,用一个空格隔开,表示一个区域的起始点和终止点的坐标。

输出格式 输出文件包括一行,这一行只包含一个整数,表示马路上剩余的树的数目。

数据范围 1L10000,1M100

输入样例

500 3
150 300
100 200
470 471

输出样例

298

二、桶排

模拟,数组遍历

O(M*L) 定义一个长度为 L+1 的布尔数组,表示每棵树的状态。

  • true 表示已经被移走;
  • false 表示未被移走;

对于每次移动树木的操作 [L_i,R_i],直接循环一遍,将布尔数组中从 L_i,到 R_i这段赋值为true

最后统计值为 false 的数量即可。

时间复杂度分析

对于每次移动树木的操作,最坏情况下区间长度是 O(L),因此计算量是 O(L),一共有 M 次操作,因此总时间复杂度是 O(M*L)=100×10000=10^6

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

const int N = 10010;

int L, m;
bool st[N];

int main() {
    cin >> L >> m;
    while (m--) {
        int l, r;
        cin >> l >> r;
        for (int i = l; i <= r; i++) st[i] = 1;
    }
    int res = 0;
    for (int i = 0; i <= L; i++)
        if (!st[i]) res++;
    printf("%d\n", res);
    return 0;
}

三、区间合并

先求出所有移动树木的操作的区间的并集,那么马路上剩余部分即为最终剩下树木的部分。 求所有区间的并集可以使用区间合并算法,可以参考 AcWing 803. 区间合并。

时间复杂度

区间合并算法的时间复杂度是 O(MlogM),其中 M 是区间数量。

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

typedef pair<int, int> PII;

const int N = 110;

int m, L;
PII seg[N];

int main() {
    cin >> L >> m;

    // 表示一个区域的起始点和终止点的坐标
    for (int i = 0; i < m; i++) cin >> seg[i].first >> seg[i].second;
    // 按起点排序
    sort(seg, seg + m);

    // L 代表马路的长度
    int res = L + 1;

    // ① 第一个区间
    int st = seg[0].first, ed = seg[0].second;
    // ② 从第二个区间枚举
    for (int i = 1; i < m; i++)
        // ③ 枚举到的起点小于等于上一个终点,即有交集
        if (seg[i].first <= ed)
            // ④ 交集分两种情况:(1)新的更长,(2)上一个更长
            ed = max(seg[i].second, ed);
        else {
            // ⑤ 没有了交点,说明断档出现,开始新的区间
            res -= ed - st + 1; // 所L+1中扣掉前一个整体区间的树的个数
            // 全新的区间开始啦
            st = seg[i].first, ed = seg[i].second;
        }
    // ⑥ 上面的逻辑,处理完成后,最后一个没有纳入到计算的范围内,所以需要再次扣除掉
    res -= ed - st + 1;
    cout << res << endl;
    return 0;
}