AcWing 4195. 线段覆盖

1,088 阅读2分钟

目录:算法日记

原题链接:4195. 线段覆盖 - AcWing题库

题目描述

在一个坐标轴上有nn条线段。

每条线段的每个端点的坐标都为整数。

可能存在退化成点的线段。

线段之间可以相互交叉、嵌套甚至重合。

请你计算,对于每个k1,2,,nk∈{1,2,…,n},坐标轴中共有多少个整数坐标的点满足恰好被 kk 条线段覆盖。

注意,左右端点分别为 li,ril_i,r_i 的线段覆盖点 xx 当且仅当 lixril_i≤x≤r_i

输入格式

第一行包含整数 nn

接下来 nn 行,每行包含两个整数 li,ril_i,r_i,表示一条线段的左右端点。

输出格式

一行 nn 个整数,其中第 ii 个整数表示坐标轴中满足恰好被 ii 条线段覆盖的整数坐标的点的数量。

数据范围

前三个测试点满足 1n31≤n≤3

所有测试点满足 1n2×1050liri10181≤n≤2×10^5,0≤l_i≤r_i≤10^{18}

输入样例

3
0 3
1 3
3 8

输出样例

6 2 1

算法思路

根据题意,整数坐标被kk条线段覆盖可理解为每个整数坐标被线段覆盖的次数

首先考虑朴素暴力做法:对每条线段覆盖的点进行遍历,遍历到每个整数点使其+1+1,最终统计每个整数点的值即可。

朴素做法时间复杂度为O(nm)O(nm),其中nn为线段数量,mm为线段长度最大值,由题目的数据范围可知,该时间复杂度必会TLE。

注意到1n2×1051≤n≤2×10^5,因此考虑O(n)O(n)O(nlogn)O(nlogn)做法。

处理每条线段相当于对线段范围内的整数点+1+1,因此利用差分可以快速实现给区间的每个数+1+1

复习下差分的主要内容:

  1. 差分是前缀和的逆运算;
  2. 差分处理区间为左闭右开,出界要记得减去多加的数;
  3. 差分使用数组存储所有整数点;

这里由于0liri10180≤l_i≤r_i≤10^{18},显然无法直接用数组进行处理,而线段条数2×105≤2×10^5,因此考虑对线段端点离散化

使用map对线段端点进行映射,遇到端点再进行处理,其中,键为区间端点,值为被区间覆盖的次数。

需要注意的是,相邻离散化端点之间的整数点被覆盖的次数一致,可直接累加。

AC代码

#include<map>
#include<cstdio>
#include<iostream>
using namespace std;
const int N = 2e5+10;
typedef long long LL;
LL res[N];
map<LL, int> dic;
int main() {
    int n;
    scanf("%ld", &n);
    for(int i = 0; i < n; ++i) {
        LL l, r;
        scanf("%ld %ld", &l, &r);
        dic[l] += 1;
        dic[r+1] -= 1;
    }
    LL sum = 0; //被覆盖的总次数
    LL last = -1; 
    for(auto& [k, v] : dic) { //map按键升序进行迭代
        if(last != -1) res[sum] += k - last; //last到k之间的整数被覆盖次数一致
        sum += v;
        last = k;
    }
    for(int i = 1; i <= n; ++i) {
        printf("%ld ", res[i]);
    }
    printf("\n");
    return 0;
}