比亚特兰蒂斯的数据要简化,同样是扫描线的入门题目,不需要对数据进行离散化,题目这次要求的是周长,和面积比起来,我们需要在线段树中增加更多成员。记录下当前线段树有效长度len和记录线段树当前线段数量的num,同时设置rb,lb标记左右节点是否被覆盖。每一次ans增加的值为当前有效长度+(上次线段数量-本次线段数量)*2。最重要的还是pushup函数,参考别人的博客看的实在有点费解,大概还需要点时间来消化吧。
#include<bits/stdc++.h> #define ll long long #define ls(i) i<<1 #define rs(i) i<<1|1 using namespace std; const int N = 2e5+10; struct node{ int l,r; // 左右下标 int len; // 区间长度 int cover; // 被覆盖多少次 int num; // 区间上有多少条线段 int lf,rf; // 左右区间值域 bool lb,rb; // 左右孩子是否被覆盖 }sgt[N<<3]; struct L{ int x,y1,y2; // 垂直x轴的线段 int state; // 入边/出边 bool operator<(L oth)const{ if(x==oth.x) return state > oth.state; return x < oth.x; } }line[N]; int y[N]; void pushup(int rt){ if(sgt[rt].cover){ // 被完全覆盖 sgt[rt].len = sgt[rt].rf - sgt[rt].lf; sgt[rt].num = 1; sgt[rt].lb = sgt[rt].rb = 1; } else if(sgt[rt].l+1==sgt[rt].r){ // 节点为叶子,且没被覆盖 sgt[rt].rb = sgt[rt].lb = 0; sgt[rt].len = sgt[rt].num = 0; }else{ // 用左右儿子更新自己 sgt[rt].rb = sgt[rs(rt)].rb; sgt[rt].lb = sgt[ls(rt)].lb; sgt[rt].len = sgt[ls(rt)].len + sgt[rs(rt)].len; sgt[rt].num = sgt[ls(rt)].num + sgt[rs(rt)].num ; if(sgt[ls(rt)].rb && sgt[rs(rt)].lb) sgt[rt].num--; // 左儿子右边被覆盖,右儿子左边被覆盖,是被同一条线段覆盖,个数-- } } // 建树 void build(int l,int r,int rt=1){ sgt[rt].l = l; sgt[rt].r = r; sgt[rt].num = 0; sgt[rt].lf = y[l]; sgt[rt].rf = y[r]; if(l+1>=r) return ; int mid = (l+r)>>1; build(l,mid,ls(rt)); build(mid,r,rs(rt)); } // 加边 void modify(int yl,int yr,int op,int rt=1){ int lf = sgt[rt].lf, rf = sgt[rt].rf; if(yl<=lf && yr>=rf){ // 被覆盖 sgt[rt].cover += op; pushup(rt); return ; } if(yl<sgt[ls(rt)].rf) modify(yl,yr,op,ls(rt)); // 落入左儿子 if(yr>sgt[rs(rt)].lf) modify(yl,yr,op,rs(rt)); // 落入右儿子 pushup(rt); } int main(){ int n,x1,x2,y1,y2; scanf("%d",&n); for(int i=1;i<=n;++i){ scanf("%d%d%d%d",&x1,&y1,&x2,&y2); y[i] = y1; y[i+n] = y2; line[i]=(L){x1,y1,y2,1}; line[i+n]=(L){x2,y1,y2,-1}; } sort(y+1,y+1+n*2); sort(line+1,line+1+n*2); int m = unique(y+1,y+1+n*2)-y-1;// 离散化 获得y值个数 build(1,m,1); // 根据y的个数建树 int ans = 0,last = 0,lines = 0; for(int i=1;i<=n*2;++i){ // 扫描 modify(line[i].y1,line[i].y2,line[i].state,1); // 加入当前边 ans += lines *2LL* (line[i].x - line[i-1].x); // 算平行于x轴的周长,每条线段贡献两次 ans += abs(sgt[1].len - last); // 计算垂直x轴的长度,根节点存的是当前扫描线的长度,与上一次做差,即为变化的长度(变长为入边,变少为出边) last = sgt[1].len; // 上一次的长度 lines = sgt[1].num; // 上一次线段个数 } printf("%d\n",ans); }