CodeForces2019B【B. All Pairs Segments】

看懂了!这是一个很有趣的题目。让我先理解一下问题:

问题理解

我们有 n 个点 x₁ < x₂ < … < xₙ,对于每一对 (i, j) 画一条线段 [xᵢ, xⱼ]。问对于给定的 k,有多少个整数坐标点恰好被 k 条线段覆盖。

关键观察:

  1. 每个线段 [xᵢ, xⱼ] 覆盖了从 xᵢ 到 xⱼ 的所有整数点
  2. 点的数量很大(x ≤ 10⁹),不能枚举所有点
  3. k 可能很大(≤ 10¹⁸)

关键洞察

对于任意整数坐标点 p,它被多少条线段覆盖?

  • 如果 p 恰好等于某个 xᵢ,那么所有以 xᵢ 为端点的线段都覆盖它
  • 如果 p 在两个给定点之间(xᵢ < p < xᵢ₊₁),那么所有左端点 ≤ xᵢ 且右端点 ≥ xᵢ₊₁ 的线段都覆盖它

覆盖次数公式

对于点 p:

  1. 如果 p = xᵢ(给定点):

    • 包含 p 作为左端点的线段数:右侧有 (n-i-1) 个点
    • 包含 p 作为右端点的线段数:左侧有 i 个点
    • 总覆盖数 = i * (n-i-1) + (n-1)
      (因为每个其他点都可以与 p 形成线段)
  2. 如果 xᵢ < p < xᵢ₊₁:

    • 只有那些左端点 ≤ xᵢ 且右端点 ≥ xᵢ₊₁ 的线段覆盖它
    • 这样的线段数 = (i+1) * (n-i-1)
      (左侧有 i+1 个点(包括 xᵢ),右侧有 n-i-1 个点(包括 xᵢ₊₁))

算法步骤

  1. 对于每个区间 [xᵢ, xᵢ₊₁]:

    • 区间内整数点个数 = xᵢ₊₁ - xᵢ - 1
    • 每个点都被 (i+1) * (n-i-1) 条线段覆盖
  2. 对于每个给定点 xᵢ:

    • 被 i*(n-i-1) + (n-1) 条线段覆盖
    • 每个这样的点只出现一次
  3. 用 map 统计每个覆盖次数出现的点数

  4. 对每个查询 k,直接输出 map[k]

C++ 实现

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

using ll = long long;

void solve() {
    int n, q;
    cin >> n >> q;
    
    vector<ll> x(n);
    for (int i = 0; i < n; i++) {
        cin >> x[i];
    }
    
    map<ll, ll> cnt;  // 覆盖次数 -> 点的个数
    
    // 处理区间内的点
    for (int i = 0; i < n - 1; i++) {
        ll leftCount = i + 1;        // 左侧点的个数(包括 x[i])
        ll rightCount = n - i - 1;   // 右侧点的个数(包括 x[i+1])
        ll coverCount = leftCount * rightCount;  // 每个区间内点的覆盖次数
        
        // 区间内的整数点个数
        ll pointsInInterval = x[i + 1] - x[i] - 1;
        if (pointsInInterval > 0) {
            cnt[coverCount] += pointsInInterval;
        }
    }
    
    // 处理给定点
    for (int i = 0; i < n; i++) {
        ll leftCount = i;                // 左侧点的个数
        ll rightCount = n - i - 1;       // 右侧点的个数
        ll coverCount = leftCount * rightCount + (n - 1);  // 每个给定点的覆盖次数
        cnt[coverCount]++;
    }
    
    // 处理查询
    for (int i = 0; i < q; i++) {
        ll k;
        cin >> k;
        cout << cnt[k] << " ";
    }
    cout << endl;
}

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    
    int t;
    cin >> t;
    while (t--) {
        solve();
    }
    
    return 0;
}

验证示例

示例1

n=2, x=[101,200]
区间 [101,200]:pointsInInterval=200-101-1=98,coverCount=1*1=1 → 98个点被1条线段覆盖
点101:leftCount=0,rightCount=1,coverCount=0*1+(2-1)=1 → 1个点被1条线段覆盖
点200:leftCount=1,rightCount=0,coverCount=1*0+(2-1)=1 → 1个点被1条线段覆盖
总共:100个点被1条线段覆盖,0个点被2条线段覆盖

示例2

n=6, x=[1,2,3,5,6,7]
区间 [1,2]:points=0
区间 [2,3]:points=0
区间 [3,5]:points=5-3-1=1,coverCount=3*3=9 → 1个点(4)被9条线段覆盖
区间 [5,6]:points=0
区间 [6,7]:points=0

点1:coverCount=0*5+(6-1)=5 → 1个点被5条覆盖
点2:coverCount=1*4+5=9 → 1个点被9条覆盖
点3:coverCount=2*3+5=11 → 1个点被11条覆盖
点5:coverCount=3*2+5=11 → 1个点被11条覆盖
点6:coverCount=4*1+5=9 → 1个点被9条覆盖
点7:coverCount=5*0+5=5 → 1个点被5条覆盖

复杂度分析

  • 时间复杂度:O(n log n + q log n),其中 log 来自 map 操作
  • 空间复杂度:O(n)

对于 n ≤ 10⁵,这个算法足够快。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Hknll

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值