洛谷P5490 【模板】扫描线

半年前就听说过一种名为扫描线的神秘科技,但实际上学过后会发现其实就是一种线段树的用法,其中扫描线的线段树还和普通的线段树有所不同,一个是每个节点实际上是映射1e9的值,其次需要同时维护两棵线段树:flag记录当先节点是否全部被覆盖(只记录最深处的值且不pushup);sum记录当先线段中扫描到的长度(需要pushup但由于每次都是只用访问sum[1]所以不用pushdown)。另外需要注意的另外一点就是线段树是只根据左节点的建立的,所以有一些操作和一般的线段树不同。

总之,扫描线原理不难但是需要考虑的细节很多,代码中已经标记,不能写错了!

#include <bits/stdc++.h>
#define x first
#define y second
#define mid (l + r >> 1)
#define lo (o << 1)
#define ro (lo | 1)
using namespace std;
typedef long long ll;
typedef long double ld;
typedef pair<int, int> pii;
typedef vector<int> vi;
const int maxn = 2e5 + 10;
const int inf = 0x3f3f3f3f;
const ll linf = 0x3f3f3f3f3f3f3f3f;
const ll mod = 998244353;
struct tri{int x,y,z;};
struct A
{
    int l,r,h,flag;
}brr[maxn];//arr代表横坐标,brr代表横着的线段信息
int n,tot,arr[maxn],flag[maxn<<2];
ll sum[maxn<<2];

void pushup(int o,int l,int r)
{
    if(flag[o])sum[o]=arr[r+1]-arr[l];
    else if(l==r)sum[o]=0;//是最下边的节点
    else sum[o]=sum[lo]+sum[ro];
}

void add(int ql,int qr,int v,int o=1,int l=1,int r=tot-1)
{
    if(ql<=l&&r<=qr)
    {
        flag[o]+=v;
        pushup(o,l,r);
        return;
    }
    if(ql<=mid)add(ql,qr,v,lo,l,mid);
    if(qr>mid)add(ql,qr,v,ro,mid+1,r);
    //这里的qr可能不是节点值,所以和一般线段树不同
    pushup(o,l,r);
}

int main()
{
    // freopen("in.txt","r",stdin);
    scanf("%d",&n);
    for(int i=0;i<n;i++)
    {
        int x1,y1,x2,y2;
        scanf("%d%d%d%d",&x1,&y1,&x2,&y2);
        arr[i*2+1]=x1,arr[i*2+2]=x2;
        brr[i*2]={x1,x2,y1,1};
        brr[i*2+1]={x1,x2,y2,-1};
    }
    sort(brr,brr+2*n,[](A a,A b){return a.h<b.h;});
    sort(arr+1,arr+2*n+1);
    tot=unique(arr+1,arr+2*n+1)-arr-1;
    //将横坐标离散化一下
    unordered_map<int,int>mp;
    for(int i=1;i<=tot;i++)
    {
        mp[arr[i]]=i;
    }
    ll ans=0;
    for(int i=0;i<2*n-1;i++)
    {
        add(mp[brr[i].l],mp[brr[i].r]-1,brr[i].flag);
        //这里不能查找右端点,因为右端点了右边的那个线段
        ans+=sum[1]*(brr[i+1].h-brr[i].h);
    }
    printf("%lld",ans);
    return 0;
}

猜你喜欢

转载自blog.csdn.net/qq_43700916/article/details/104131667