扫描线
题目链接
考虑将每个矩形看做两次操作,分别是在$Y$轴上叠加线段和去除线段。按$X$坐标排序后依次访问扫过。
注意:本题中,坐标表示的是直角坐标系的整点坐标,也即我们计算的是连续的面积,不过这里矩形都是水平的。
那么离散化一波,每个离散点表示到它的后继之间的线段。
考虑使用线段树,维护“覆盖计数”和“有效线段长”。
本题的线段树虽然是区间修改区间查询,但注意到本题的区间查询实际上都只在询问全区间,也即,如果写传统的区间询问函数,访问完根节点,都不会往下递归。
那么不需要做“覆盖计数”的懒标记了,只要每次精准定位$O(\log n)$个区间修改,注意每次回溯都维护一下“有效线段长”就行了。
具体方案是,如果一个区间被全覆盖了,那么它的“有效线段长”就是区间原长度。否则就是两个子区间的答案相加。
这里有一个细节,当你访问未被覆盖或者正好被去除覆盖的叶子结点时,你会尝试访问它的子区间。这显然是错误的,要么特判该情况时“有效线段长”为$0$,要么将数组大小翻倍来无视该情况。
代码(100分):
#include<iostream> #include<cstdio> #include<cstring> #include<cmath> #include<algorithm> #include<vector> #include<queue> #include<map> #include<set> #define IL inline #define RG register #define _1 first #define _2 second using namespace std; typedef long long LL; const int N=2e5; int n,m,a[N+3]; struct Lne{ int d,x,y,t; Lne(int d=0,int x=0,int y=0,int t=0) :d(d),x(x),y(y),t(t){} }p[N+3]; IL bool operator<(Lne x,Lne y){ return x.d<y.d; } IL int sol(int x){ int l=1,r=m,mid,ans; while(l<=r){ mid=(l+r)>>1; if(x<=a[mid]){ r=mid-1; ans=mid; } else l=mid+1; } return ans; } IL void discrete(){ n<<=1; sort(a+1,a+n+1); m=1; for(int i=2;i<=n;i++) if(a[i]!=a[i-1]) a[++m]=a[i]; for(int i=1;i<=n;i+=2){ p[i].x=p[i+1].x=sol(p[i].x); p[i].y=p[i+1].y=sol(p[i].y); } } int c[N*4+3],s[N*4+3]; IL int ls(int i){return i<<1;} IL int rs(int i){return i<<1|1;} IL void merge(int i,int l,int r){ if(c[i]) s[i]=a[r+1]-a[l]; else if(l==r) s[i]=0; else s[i]=s[ls(i)]+s[rs(i)]; } void mdf(int i,int l,int r,int ql,int qr,int x){ if(ql<=l&&r<=qr){ c[i]+=x; merge(i,l,r); return ; } int mid=(l+r)>>1; if(ql<=mid) mdf(ls(i),l,mid,ql,qr,x); if(qr>mid) mdf(rs(i),mid+1,r,ql,qr,x); merge(i,l,r); } int main(){ scanf("%d",&n); for(int i=1;i<=n;i++){ int x1,y1,x2,y2; scanf("%d%d%d%d",&x1,&y1,&x2,&y2); a[i*2-1]=y1; a[i<<1]=y2; p[i*2-1]=Lne(x1,y1,y2,1); p[i<<1]=Lne(x2,y1,y2,-1); } discrete(); sort(p+1,p+n+1); LL ans=0; for(int i=1;i<n;i++){ mdf(1,1,m,p[i].x,p[i].y-1,p[i].t); ans+=1LL*s[1]*(p[i+1].d-p[i].d); } printf("%lld",ans); return 0; }