题解:洛谷 P1496 火烧赤壁

【题目来源】

洛谷:P1496 火烧赤壁 - 洛谷

【题目描述】

曹操平定北方以后,公元 \(208\) 年,率领大军南下,进攻刘表。他的人马还没有到荆州,刘表已经病死。他的儿子刘琮听到曹军声势浩大,吓破了胆,先派人求降了。

孙权任命周瑜为都督,拨给他三万水军,叫他同刘备协力抵抗曹操。

隆冬的十一月,天气突然回暖,刮起了东南风。

没想到东吴船队离开北岸大约二里距离,前面十条大船突然同时起火。火借风势,风助火威。十条火船,好比十条火龙一样,闯进曹军水寨。那里的船舰,都挤在一起,又躲不开,很快地都烧起来。一眨眼工夫,已经烧成一片火海。

曹操气急败坏的把你找来,要你钻入火海把连环线上着火的船只的长度统计出来!

给定每个起火部分的起点和终点,请你求出燃烧位置的长度之和。

【输入】

第一行一个整数,表示起火的信息条数 \(n\)
接下来 \(n\) 行,每行两个整数 \(a, b\),表示一个着火位置的起点和终点(注意:左闭右开)。

【输出】

输出一行一个整数表示答案。

【输入样例】

3
-1 1
5 11
2 9

【输出样例】

11

【核心思想】

  1. 问题分析:给定 \(n\) 个区间 \([a_i, b_i)\)(左闭右开),求这些区间的并集的总长度。直接累加会有重叠部分重复计算,因此需要先对区间按起点排序,然后合并重叠或相邻的区间,最后累加合并后区间的长度。

  2. 算法选择

    • 贪心 + 区间合并:将所有区间按左端点升序排序。维护当前合并区间的左右端点 st, ed。遍历排序后的区间,若当前区间的左端点 ≤ ed(有重叠或连续),则更新 ed = max(ed, r);否则,将当前合并区间长度累加,并开始新的合并区间。最终累加所有合并区间的长度。
    • 复杂度:排序 \(O(n \log n)\),合并 \(O(n)\),总体 \(O(n \log n)\)
  3. 关键步骤

    • 读入 \(n\) 以及 \(n\) 个区间的起点和终点。
    • 按左端点从小到大排序。
    • 初始化 st = 第一个区间的左端点ed = 第一个区间的右端点sum = 0
    • 对于每个后续区间 \([l, r]\)
      • \(l \le ed\),则与当前合并区间重叠,扩展右端点 ed = max(ed, r)
      • 否则,将 ed - st 累加到 sum,并将 st = l, ed = r
    • 最后将最后一个合并区间的长度累加。
    • 输出 sum
  4. 时间/空间复杂度

    • 时间复杂度:\(O(n \log n)\),排序主导。
    • 空间复杂度:\(O(n)\),存储区间。
  5. 贪心 + 区间合并的核心思想

    • 排序保证有序扫描:按左端点排序后,可以依次处理,确保当前区间是后续所有可能重叠区间中左端点最小的。
    • 重叠判定:只要新区间的起点 ≤ 当前合并区间的终点,则必然重叠(左闭右开,等于也算重叠)。
    • 贪心扩展:合并时只需更新右端点,因为左端点已经固定。
    • 适用场景:适用于计算多个区间并集长度、覆盖总长度等问题,是经典的区间合并模板。

【算法标签】

普及- #贪心

【代码详解】

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

#define int long long  // 定义int为long long类型

int n;                  // 线段数量
int st, ed;             // 当前合并区间的起点和终点
int sum;                // 合并后的总长度

// 线段结构体
struct Line
{
    int l, r;           // 线段的起点和终点

    // 重载小于运算符,用于排序
    bool operator < (Line &t)
    {
        return l < t.l;
    }
} a[20005];             // 存储所有线段

signed main()
{
    // 输入线段数量
    cin >> n;

    // 输入每条线段的起点和终点
    for (int i = 1; i <= n; i++)
    {
        cin >> a[i].l >> a[i].r;
    }

    // 按线段起点排序
    sort(a + 1, a + n + 1);

    // 初始化第一个合并区间
    st = a[1].l;
    ed = a[1].r;
    sum += a[1].r - a[1].l;

    // 遍历处理每条线段
    for (int i = 2; i <= n; i++)
    {
        // 如果当前线段与合并区间有重叠
        if (a[i].l <= ed)
        {
            // 完全被包含则跳过
            if (a[i].r < ed)
            {
                continue;
            }
            // 否则扩展合并区间
            else
            {
                st = ed;            // 更新起点为原终点
                ed = a[i].r;        // 更新终点为新线段的终点
                sum += ed - st;     // 增加扩展部分的长度
            }
        }
        // 如果没有重叠,创建新的合并区间
        else
        {
            st = a[i].l;
            ed = a[i].r;
            sum += ed - st;
        }
    }

    // 输出合并后的总长度
    cout << sum << endl;

    return 0;
}
// 使用acwing模板二刷
#include <bits/stdc++.h>
using namespace std;

#define int long long  // 定义int为long long类型
const int N = 2005;    // 定义最大线段数量
typedef pair<int, int> PII;  // 定义线段类型为pair

int n;                  // 线段数量
int sum;                // 合并后的总长度
vector<PII> segs;       // 存储所有线段的容器

/**
 * 合并重叠或相邻的线段
 * @param segs 待合并的线段集合(引用传递)
 */
void merge(vector<PII> &segs)
{
    vector<PII> res;    // 存储合并后的结果
    sort(segs.begin(), segs.end());  // 按线段起点排序

    int st = -2e9, ed = -2e9;  // 初始化当前合并区间为极小值

    // 遍历所有线段
    for (auto seg : segs)
    {
        // 如果当前线段与合并区间无重叠
        if (ed < seg.first)
        {
            // 如果不是初始状态,保存当前合并区间
            if (st != -2e9)
                res.push_back({st, ed});
            // 开始新的合并区间
            st = seg.first;
            ed = seg.second;
        }
        // 如果有重叠,扩展当前合并区间
        else
            ed = max(ed, seg.second);
    }

    // 保存最后一个合并区间
    if (st != -2e9)
        res.push_back({st, ed});

    segs = res;  // 更新线段集合为合并后的结果
}

signed main()
{
    // 输入线段数量
    cin >> n;

    // 输入每条线段的起点和终点
    for (int i = 1; i <= n; i++)
    {
        int l, r;
        cin >> l >> r;
        segs.push_back({l, r});
    }

    // 合并线段
    merge(segs);

    // 计算合并后的总长度
    for (int i = 0; i < segs.size(); i++)
        sum += segs[i].second - segs[i].first;

    // 输出结果
    cout << sum << endl;

    return 0;
}

【运行结果】

3
-1 1
5 11
2 9
11
posted @ 2026-09-02 22:51  团爸讲算法  阅读(11)  评论(0)    收藏  举报