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;
}