【扫描线+树状数组或者线段树】Codeforces Round #665 (Div. 2) E - Divide Square

E - Divide Square

题目描述

1000000*1000000大小的正方形,四个顶点分别是(0,0)(0,1e6)(1e6,0)(1e6,1e6)
矩形内横着画n(n<=1e5)条线段,竖着画m(m<=1e5)条线段,
保证每条线段的一个端点落在正方形一条边上,保证没有两条线段在同一条直线上
求线段把正方形分成了多少个区域

思路

一眼能看出来是扫描线 但是我这个弱鸡很难想到如何做。看了题解,说一下大致思路:
如何扫描?将水平线段左右端点分别看做事件(event数组存储),按照左端点排序;垂直线段用verLine数组存储,按照横坐标进行排序。
因为每个线段必定交四个边中至少一个,所以两个互相垂直的线段如果在正方形内相交,必定贡献1答案(有个大佬例子很生动,就好比剪纸,在角上横竖来两刀就多出一个矩形)。那么我们只需要维护每个垂直的线段和几个水平线相交,所有的交点加上起初值就是最终答案。这里的起初值指的是贯通正方形的水平线的个数再+1。+1是因为没有贯通的话起初就有一个大正方形。

实现

从左往右扫描垂直线,开一个树状数组维护扫描到该垂直线时,和水平线有多少交点。当遇到水平线的左端点时,c[y] ++; 当遇到水平线的右端点时c[y] --。L1交点有三个,那么c[1e6] = 3,L2交点有两个 此时c[1e6] = 2. 累加即可。

在这里插入图片描述
现在解释这句话:

当遇到水平线的左端点时,c[y] ++; 当遇到水平线的右端点时c[y] –

在这里插入图片描述
这样保证了:每次扫描到一个垂直线,可以直接求出这根垂直线在 y1~y2 之间有多少交点,伪代码就是query(y2) - query(y1)。

代码

有些边界问题很恶心 需要慢慢调

#include <bits/stdc++.h>
using namespace std;
#define ll long long
const int N = 1e6 + 10;
int n, m;

ll ans = 1;
vector<array<int, 3> > event;
vector<array<int, 3> > verLine;

ll c[N];

void modify(int x, int val) {
    for (int i = x; i <= 1e6 + 5; i += i & (-i)) {
        c[i] += val;
    }
}
int query(int x) {
    int sum = 0;
    for (int i = x; i >= 1; i -= i & (-i)) {
        sum += c[i];
    }
    return sum;
}
int main()
{
    int n, m;
    scanf("%d%d",&n, &m);
    for(int i = 1;  i <= n; i ++)
    {
        int y, lx, rx;
        scanf("%d%d%d",&y, &lx, &rx);
        if(lx == 0 && rx == 1e6) ans ++; // 初始块儿数
        // 存储水平线
        event.push_back({lx, y + 1, 1});
        event.push_back({rx + 1,y + 1,-1});
    }
    for(int i = 1;  i <= m; i ++)
    {
        int x, ty, by;
        scanf("%d%d%d",&x, &by, &ty);
        // if(ty == 1e6 && by == 0) ans ++;
        // 存储垂线
        verLine.push_back({x, by, ty});
    }
    sort(event.begin(), event.end());
    sort(verLine.begin(), verLine.end());
    modify(1,1), modify(1e6+1,1);
    // 还是没有体会到扫描线的本质
    // 我现在在扫谁?我扫的是垂直线, 它的贡献怎么算? 
    // 把垂线左边的水平线都操作完,直接到垂线这里算贡献就行
    // 需要把水平线的贡献记录到垂直的维度上维护
    for(int j = 0, i = 0; i < m; i ++)
    {
        // 计算扫描前的贡献
        while (j < event.size() && event[j][0] <= verLine[i][0])
        {
            modify(event[j][1], event[j][2]);
            j++;
        }
        // 查询 计算贡献
        ans += query(verLine[i][2] + 1) - query(verLine[i][1]) - 1;
    }
	printf("%lld\n", ans);
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

善良的大铁牛

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

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

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

打赏作者

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

抵扣说明:

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

余额充值