• 【Luogu P5490】【模板】扫描线


    题目大意:

    (n) 个矩形的面积并。

    正文:

    本题计算面积并在扫描线中较为简单。抛开离散化,我们着重讲扫描线。

    图1

    与上图为例,思考怎么计算它们的几何并。

    暴力

    用一个数组来存信息,覆盖了就标 1,否则标 0。暴力既好想又好写,但当坐标一大,时空都会超。

    容斥

    用总面积减去重合面积。但只局限于重合少的题。

    分割图形

    将图形重新分割成一个个规则的矩形,用线段树维护一条扫描的线。

    如上图,每扫描到一段,该段面积就是直线上覆盖的长度乘该段的宽度。

    顺着这个思路来,用一个四元组 ((x,y_1,y_2,flag)) 记录每一条竖线,表示竖线的坐标及是否是左右边界。

    线段树维护扫描线上被覆盖的长度,每次修改后,更新被覆盖长度(如下)。

    void pushup(int x)
    {
    	if(t[x].cnt) t[x].len = disx[t[x].r + 1] - disx[t[x].l]; 
    	else t[x].len = t[x * 2].len + t[x * 2 + 1].len;
    }
    

    对于线段树的标记,这里引入 李煜东的《算法竞赛》,在本题我们只关心整个扫描线(线段树根节点)上被覆盖的长度。四元组又成对出现,所以线段树区间修改也是重复出现,这样就没必要下传延时标记,而采用更加简单的做法:在线段树每个节点上另加维护该节点代表的区间被矩形覆盖的长度 (len),该节点自身被覆盖的次数 (cnt)。对于一个四元组 ((x,y_1,y_2,flag)),在 ([val(y_1),val(y_2)-1]) 上执行区间修改。该区间被线段树划分成 (log N) 个节点,把这些节点的 (cnt) 都加 (k)

    对于线段树任意一个节点 ([l,r]),若 (cnt>0),则 (len) 就等于两子节点 (len) 之和。在一个节点 (cnt) 被修改,以及线段树传递信息时,我们都按照方法更新 (len) 值。根节点 (len) 值就是整个扫描线被覆盖的长度。

    代码:

    const int N = 2000000 + 10;
    int n;
    
    struct SegmentTree
    {
    	int l, r;
    	ll cnt, len;
    }t[N << 2];
    ll x[N], disx[N], y[N];
    
    struct node
    {
    	ll xd, xu, y, flag;
    }a[N << 2];
    
    bool cmp(node a, node b)
    {
    	if(a.y == b.y) return a.flag > b.flag;
    	return a.y < b.y;
    }
    
    void build(int x, int l, int r)
    {
    	t[x].l = l, t[x].r = r;
    	if(l == r) return;
    	int mid = (t[x].l + t[x].r) / 2;
    	build(x * 2, l, mid);
    	build(x * 2 + 1, mid + 1, r);
    }
    
    void pushup(int x)
    {
    	if(t[x].cnt) t[x].len = disx[t[x].r + 1] - disx[t[x].l]; 
    	else t[x].len = t[x * 2].len + t[x * 2 + 1].len;
    }
    
    void change(int x, int l, int r, ll k)
    {
    	if(l <= t[x].l && t[x].r <= r)
    	{
    		t[x].cnt += k;
    		pushup(x);
    		return;
    	}
    	int mid = (t[x].l + t[x].r) / 2;
    	if(l <= mid) change(x * 2, l, r, k);
    	if(r > mid) change(x * 2 + 1, l, r, k);
    	pushup(x);
    }
    
    ll ans;
    
    int main()
    {
    	scanf("%d", &n);
    	int m = n * 2;
    	for (int i = 1; i <= n; i ++)
    	{
    		scanf("%lld%lld%lld%lld", &x[(i << 1) - 1], &y[(i << 1) - 1], &x[(i << 1)], &y[(i << 1)]);
    		a[(i << 1) - 1].xd = a[(i << 1)].xd = x[(i << 1) - 1];
    		a[(i << 1) - 1].xu = a[(i << 1)].xu = x[(i << 1)];
    				
    		a[(i << 1) - 1].y = y[(i << 1) - 1];
    		a[(i << 1)].y = y[(i << 1)];
    		a[(i << 1) - 1].flag = 1;
    		a[(i << 1)].flag = -1;
    	}
    	sort(x + 1, x + 1 + m);
    	m = unique(x + 1, x + 1 + m) - (x + 1);
    	for (int i = 1; i <= n * 2; i++)
    	{
    		int p1 = lower_bound(x + 1, x + 1 + m, a[i].xd) - x,
    			p2 = lower_bound(x + 1, x + 1 + m, a[i].xu) - x;
    		disx[p1] = a[i].xd, disx[p2] = a[i].xu;
    		a[i].xd = p1;
    		a[i].xu = p2;
    	}
    	m = 2 * n;
    	build(1, 1, m);
    	sort(a + 1, a + 1 + m, cmp);
    	for (int i = 1; i <= m; i++)
    	{
    		change(1, a[i].xd, a[i].xu - 1, a[i].flag);
    		ans += t[1].len * (a[i + 1].y - a[i].y);
    	}
    	printf("%lld
    ", ans);
        return 0;
    }
    
  • 相关阅读:
    关于prototype学习
    java io 从文件的读取和输入
    java 匿名内部类
    动手做个 AI 机器人,帮我回消息!
    几个高效做事的法则,让你的一天有 25 小时
    爆肝一个月,我做了个免费的面试刷题网
    Log4j 被曝核弹级漏洞,开发者炸锅了!
    几个超火的编程网站,别错过!
    Java 处理表格,真的很爽!
    c++智能指针转化:static_pointer_cast、dynamic_pointer_cast、const_pointer_cast、reinterpret_pointer_cast
  • 原文地址:https://www.cnblogs.com/GJY-JURUO/p/13357034.html
Copyright © 2020-2023  润新知