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)。
有些边界问题很恶心 需要慢慢调
#includeusing namespace std; #define ll long long const int N = 1e6 + 10; int n, m; ll ans = 1; vector > event; vector > 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("%lldn", ans); return 0; }



