嘘~ 正在从服务器偷取页面 . . .

CF1401E Divide Square 题解


CF1401E Divide Square 题解

题目链接:CF1401E Divide Square

题意

给定一个 \(10^6\times10^6\) 的正方形,\(n\) 条横线和 \(m\) 条竖线穿过了它,求这些线把正方形分成了多少个部分。

保证不存在两条线段在同一条线上,并且每个线段至少与正方形的一条边相交。

输入格式

第一行包含两个整数 \(n\)\(m\) ( \(0 \leq n, m \leq 10^5\) ) —— 水平线段和垂直线段的数量。

接下来的 \(n\) 行包含水平线段的描述。第 \(i\) 行包含三个整数 \(y_i\)\(lx_i\)\(rx_i\) \((0 < y_i < 10^6,~0 \leq lx_i < rx_i \leq 10^6)\),表示该线段连接 \((lx_i, y_i)\)\((rx_i, y_i)\)

接下来的 \(m\) 行包含垂直线段的描述。第 \(i\) 行包含三个整数 \(x_i\)\(ly_i\)\(ry_i\) \((0 < x_i < 10^6,~0 \leq ly_i < ry_i \leq 10^6)\),表示该线段连接 \((x_i, ly_i)\)\((x_i, ry_i)\)

输出格式

打印在绘制所有线段后正方形被分割成的部分数量。

tips

The sample is like this:

二维偏序问题的通常解法是枚举一维、维护一维

在本题中并不能直接套用这种方法,但是基本上还是这个思路。

首先有一个结论,对于一条竖线,它与其他线段的交点总数为它的贡献。

除了一种特殊的情况,就是任何将大正方形直接切成两半的线段,会多生成一个部分。

这里说的任何是指,如果我们只计算竖边产生的贡献,那么特殊的横边会产生 \(1\) 的贡献(其他则不会)

如果我们对横边进行某种维护,然后枚举竖边,查询每个竖边能对答案造成多少贡献。

考虑把横坐标看作时间 \(t\) ,维护每个纵坐标 \(i\)​ 在当前时刻是否被线段覆盖。

如果将横边视作修改操作,竖边视作查询操作,则可以用「单点修改 + 区间查询」树状数组维护。

我们把修改和询问分别按时间排序,如果当前时刻同时有修改和查询,就先修改后查询。

时间复杂度 \(\mathcal{O}(n \log n)\)

代码:

#include <bits/stdc++.h>
using namespace std;
#define int long long
#define INF 0x3f3f3f3f3f3f3f3f
void up(int &x,int y) { x < y ? x = y : 0; }
void down(int &x,int y) { x > y ? x = y : 0; }
int lowbit(int x) { return x & (-x); }
#define N ((int)(1e5 + 15))
#define M ((int)(1e6 + 15))

int n, m, res = 1, tot1, tot2;
int tr[M], y[N], x[N], lx[N], rx[N], ly[N], ry[N];
struct node1 { int t, p, v; } q1[N * 2];
struct node2 { int t, l ,r; } q2[N * 2];
bool operator<(node1 a, node1 b) { return a.t < b.t; }
bool operator<(node2 a, node2 b) { return a.t < b.t; }
void add(node1 now) { for(int i = now.p; i <= 1e6; i += lowbit(i)) tr[i] += now.v; }
int sum(int x, int r = 0) { for(int i = x; i; i -= lowbit(i)) r += tr[i]; return r; }
int qry(node2 now) { return sum(now.r) - sum(now.l - 1); }
signed main()
{
    ios::sync_with_stdio(0);
    cin.tie(0);cout.tie(0);
    // freopen("check.in","r",stdin);
    // freopen("check.out","w",stdout);
    cin >> n >> m;
    for(int i = 1; i <= n; i++)
    {
        cin >> y[i] >> lx[i] >> rx[i];
        if(lx[i] == 0 && rx[i] == 1e6) ++res;
        ++y[i]; ++lx[i]; ++rx[i];
        q1[++tot1] = {lx[i] - 1, y[i], 1};
        q1[++tot1] = {rx[i], y[i], -1};
    }
    for(int i = 1; i <= m; i++)
    {
        cin >> x[i] >> ly[i] >> ry[i];
        if(ly[i] == 0 && ry[i] == 1e6) ++res;
        ++x[i]; ++ly[i]; ++ry[i];
        q2[++tot2] = {x[i], ly[i], ry[i]};
    }
    sort(q1 + 1, q1 + 1 + tot1); sort(q2 + 1, q2 + 1 + tot2);
    int cnt1 = 1, cnt2 = 1;
    for(; cnt1 <= tot1 && q1[cnt1].t == 0; ++cnt1) add(q1[cnt1]);
    for(int t = 1; t <= 1e6; t++)
    {
        for(; cnt2 <= tot2 && q2[cnt2].t == t; ++cnt2) res += qry(q2[cnt2]);
        for(; cnt1 <= tot1 && q1[cnt1].t == t; ++cnt1) add(q1[cnt1]);
    }
    cout << res << '\n';
    return 0;
}

参考文献

[1] https://www.luogu.com.cn/article/bcfrp3ke


题外话

好像在我眼里线段树只能维护序列一样,为什么会没想到呢?


文章作者: q779
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-ND 4.0 许可协议。转载请注明来源 q779 !
评论
  目录