• 【线段树+扫描线】P5490 【模板】扫描线


    P5490 【模板】扫描线

    首先有这么一张图,要求它的面积并。

    我们想,如果可以有一条扫描线从下往上扫,记录它所扫到的边的长度(并)(len),再求出这条边与即将扫到的下一条边的距离(h),那么我们就可以求出第一块面积(紫色)(S_1=len imes h)

    然而如何求出这个(len)

    显然只用当前边的长度是不行的,如:当扫到边(r_2)的时候,(len)应该是(250-100=150)这么长

    什么情况下应该把之前扫过的边的长度也考虑进去?

    我们注意到,当一个矩形只有下边被扫过的时候,对这个矩形的面积计算仍未结束,那么这个矩形的下边长度应该被算进(len)中。当这个矩形的上边被扫过之后,它的下边长度不需要再考虑到(len)中了。

    因此我们需要对矩形的上下边进行区分:将下边标记为1,上边标记为-1。

    当扫到边(r_2)时,(r_2)(r_1)有重叠部分。新的问题又来了:如何不重不漏地计算这个(len)

    我们将所有矩形的端点投影到x轴上,结果如下图:

    当扫到边(r_1)时,区间[x1,x2]与[x2,x3]被覆盖了。我们记录下区间的被覆盖次数。

    当扫到边(r_2)时,区间[x2,x3]与[x3,x4]被覆盖了。这时区间的被覆盖次数:

    (cnt_{[x1,x2]}=1)(cnt_{[x2,x3]}=2)(cnt_{[x3,x4]}=1)

    那么我们只需要统计(cnt)正数的区间,然后将这些区间的长度相加即可得到(len)

    此时(len=L_{[x1,x2]}+L_{[x2,x3]}+L_{[x3,x4]}=150)

    (r_2)与下一条边的距离(h=50),则第二块面积​(绿色)(S_2=len imes h)

    当扫到边(r_3)时,我们知道这是左边那个矩形的上边,它的标记是-1。

    被它覆盖的区间有[x1,x2]和[x2,x3]。

    遇到(r_3)意味着我们不再需要考虑(r_1)的长度了,如何在计算中体现这个“不再考虑”?很简单,因为我们之前统计过区间的被覆盖次数,“不再考虑”时将对应区间的覆盖次数扣除(-1)即可。

    更新之后,区间的被覆盖次数如下:

    (cnt_{[x1,x2]}=0)(cnt_{[x2,x3]}=1)(cnt_{[x3,x4]}=1)

    同样,我们再一次统计(cnt)是正数的区间,然后将这些区间的长度相加即可得到(len)

    此时(len=L_{[x2,x3]}+L_{[x3,x4]}=100)

    (r_3)与下一条边的距离(h=55),则第三块面积(红色)(S_3=len imes h)

    我们知道(r_4)是整个图里剩下的最后一条与x轴平行的边,所以当扫描线扫过(r_3)时,面积计算就结束了,不需要再扫过(r_4)

    那么最后的面积并(ans=S_1+S_2+S_3)

    过程捋清楚了,那么落实到代码上,又应该用什么数据结构来储存最关键的、由所有矩形的端点在x轴上的投影所划分出的区间呢?

    在上述过程中,我们的计算都是具体到如[x1,x2]、[x2,x3]这样的最小区间。这显然是可优化的:例如,在扫描到r_3时,可以只在[x1,x3]这个更大区间上作统计,而不必进一步细分。

    这样的性质可以联想到线段树

    在模板线段树中,一个结点代表的是区间([l,r])上的若干个点。特别地,在叶子结点中(l=r),此时([l, r ])代表的是1个点。

    本题要求我们线段树存的是线段,不能是点。所以我们改变结点的定义:结点([l,r])代表的是线段([l,r+1])。如此,线段树的叶子节点([l,r](l=r))代表的就是最小的区间,如([x_1,x_2])([x_4,x_5])这样的区间。

    还有一个问题:离散化。考虑题目中的数据范围:1e9,显然这要求我们对所有矩形的端点的x值进行离散化(也就是重新编号)。再将实际x值储存到离散化后对应下标即可,如X[1]=100,X[2]=150.

    struct Tree{
    	int l,r,cnt,len;
    }tree[maxn*4];
    

    建树过程和模板线段树区别不大:

    void build(int i,int l,int r){
    	tree[i].l=l;
    	tree[i].r=r;
    	if(l==r) return;
    	int mid=(l+r)>>1;
    	build(i*2,l,mid);
    	build(i*2+1,mid+1,r);
    }
    

    扫描到新的一条与x轴平行的边后的对len的更新操作(最后的更新结果是tree[1].len):

    void update(int i){
    	int l=tree[i].l,r=tree[i].r;
    	if(tree[i].cnt) tree[i].len=X[r+1]-X[l];
      //如果tree[i]所代表的区间被矩形完全覆盖,直接计算该区间的长度
    	else{
    		tree[i].len=tree[i*2].len+tree[i*2+1].len;
        //不完全覆盖/没有覆盖,用其子结点的长度并
    	}
    }
    
    void change(int i,LL L,LL R,int c){
      //L、R是真实区间
      //l,r仅为端点下标
    	int l=tree[i].l,r=tree[i].r;
    	if(X[r+1]<=L||X[l]>=R) return;
      //如果下标所代表的区间完全不在[L,R]范围内,就返回
      if(X[l]>=L&&X[r+1]<=R){
        //如果下标所代表的区间被[L,R]完全覆盖
        tree[i].cnt+=c;
        //更新被覆盖次数
        update(i);
        //计算新的len
        return;
      }
      //如果下标代表的区间被[L,R]部分覆盖,找子结点,再计算新的len
      change(i*2,L,R,c);
      change(i*2+1,L,R,c);
      update(i);
    }
    
    struct ScanLine{
    	LL l,r,h;
    	//保存线的左右端点(下标),及其高度(y值)
    	int mark;
    	//保存线的权值
    	bool operator < (const ScanLine &t) const{
    		return h<t.h;
    		//按y值从小到大排序
    	}
    }line[maxn*2];
    //line数组保存所有与x轴平行的线的信息
    

    完整代码如下:

    typedef long long LL;
    const int maxn = 1000000 + 10;
    LL n, x, y, xx, yy;
    LL X[maxn * 2];
    
    struct ScanLine {
        LL l, r, h;
        int mark;
        bool operator < (const ScanLine& t)const {
            return h < t.h;
        }
    }line[maxn*2];
    
    struct Tree {
        int l, r, cnt;
        LL len;
    }tree[maxn*4];
    
    void build(int i, int l, int r) {
        tree[i].l = l;
        tree[i].r = r;
        if (l == r) return;
        int mid = (l + r) >> 1;
        build(i * 2, l, mid);
        build(i * 2 + 1, mid + 1, r);
    }
    
    void update(int i) {
        int l = tree[i].l;
        int r = tree[i].r;
        if (tree[i].cnt) tree[i].len = X[r + 1] - X[l];
        else {
            tree[i].len = tree[i * 2].len + tree[i * 2 + 1].len;
        }
    }
    
    void change(int i, LL L, LL R, int m) {
        int l = tree[i].l;
        int r = tree[i].r;
        if (X[l] >= R || X[r + 1] <= L) return;
        if (X[l] >= L && X[r + 1] <= R) {
            tree[i].cnt += m;
            update(i);
            return;
        }
        change(i * 2, L, R, m);
        change(i * 2 + 1, L, R, m);
        update(i);
    }
    
    int main()
    {
        //ios::sync_with_stdio(false);
        //while (scanf("%d%d",&n,&m)!=EOF){
        cin >> n;
        for (int i = 1; i <= n; i++) {
            cin >> x >> y >> xx >> yy;
            X[2 * i - 1] = x;
            X[2 * i] = xx;
          //注意这里已经完成了离散化的第一步:重编号
            ScanLine t;
            t.l = x; t.r = xx; t.h = y; t.mark = 1;
            line[2 * i - 1] = t;
            t.h = yy; t.mark = -1;
            line[2 * i] = t;
        }
      //显然我们读入了2*n条边
        n *= 2;
        sort(line + 1, line + n + 1);
      //将所有线从低到高排序
        sort(X + 1, X + n + 1);
        int tail = unique(X + 1, X + n + 1) - (X + 1);
      //离散化第二步:去重(unique函数要求数组有序)
        build(1, 1, tail - 1);
      //考虑线段树结点的定义,就知道最右边那个端点不应该包含在内
      //故范围是1到tail-1
        LL ans = 0;
        //从下往上扫
        for (int i = 1; i < n; i++) {
            change(1, line[i].l, line[i].r, line[i].mark);
            ans += tree[1].len * (line[i + 1].h - line[i].h);
            //len的最终更新结果在tree[1]中
        }
        cout << ans;
        return 0;
    }
    
    
  • 相关阅读:
    【ZOJ3195】Design the City-LCA算法
    【ZOJ3195】Design the City-LCA算法
    【POJ1330】Nearest Common Ancestors-LCA算法
    【POJ1330】Nearest Common Ancestors-LCA算法
    【POJ3237】Tree-树链剖分
    【POJ3237】Tree-树链剖分
    【SPOJ375】Query on a tree-树链剖分
    perl 回调必须是函数引用
    运行复制的ZooKeeper 部署
    运行复制的ZooKeeper 部署
  • 原文地址:https://www.cnblogs.com/streamazure/p/13616436.html
Copyright © 2020-2023  润新知