->题目请戳这里<-
题目大意:中文题,不解释。
题目分析:求矩形面积并,线段树搞之。但是这题不是简单求面积并,而是求覆盖2次及以上的面积并。一开始直接写个二维线段树,尽快已经尽量做到节约空间,最后还是爆了,1000个矩形,二维线段树伤不起。。。然后换个思路,换成一维线段树+线扫描,仿照之前写的一个面积并的题目,结果又是各种跪。然后分析了一下原因,因为我一开始所谓的线扫描法简直弱爆了,那就不叫线扫描,只是看着像而已,我是把每个矩形分成一段一段的插入,这样的话,一个矩形如果x方向上距离足够大的话,所产生的扫描线数量就会很多,n个的话就更多了。学习了别人的代码后发现,其实n个矩形只要2n条扫描线就够了,不过要记得矩形的左右界,左界为1,右界为-1,那么每个矩形有2条扫描线,直接往线段树里面插,如果扫到了一个矩形的左界,那么这个矩形就插到线段树中,更新每个节点的y方向上的长度并,如果扫到一个矩形的右界,那么将-1插入线段树,就相当于将这个矩形删除了。
不过这题的难点在于要求覆盖至少2次的面积并,搜了几个代码基本都一样,也解释的不是很清楚,所以这里尽量解释清楚些。
线段树的每个节点我们需要用到3个变量:flag:记录当前区间覆盖的矩形数;one:记录当前区间只覆盖了一次的长度;len:记录当前区间覆盖了2次及2次以上的长度。这里注意one表示的长度和len表示有效长度是没有交集的。
flag值有3种情况:1:flag = 0,表示当前区间没有完全覆盖,注意没有完全覆盖并不表示当前区间没有矩形覆盖,只是没有恰好完全覆盖当前区间的矩形,也许当前区间的子区间就有覆盖的矩形;2:flag = 1,表示当前区间完全被矩形覆盖了1次;3:flag >1,表示当前区间被完全覆盖2次及以上,那么这个区间整个长度就是我们要的。
对于one,当前区间恰好覆盖了一次的情况,那么当前区间满足覆盖了2次及以上的长度就分2部分,第一部分是当前区间子区间中恰好覆盖了一次的长度,即左右子区间中one的长度,因为当前区间恰好被完全覆盖了一次,而子区间中也有恰好被覆盖了1次的,那么子区间中恰好被覆盖了一次的实际上已经被覆盖2次了;第二部分就是当前区间左右子区间中已经被覆盖了2次及以上的区间,这2部分构成了当前区间的有效长度len。
我们插入一条扫描线的时候,如果找到当前区间就是我们要插入的区间,那么这个区间覆盖次数+flag(该扫描线边界,左边界为1,右边界为-1,+1就说明加入了一个矩形,-1说明这个矩形已经在扫描线左边了,删之)。每插入一条扫描线,更新节点的有效长度值,直到根节点。最后算出面积并。
ps:注意这题题目描述有误,给的数据点是坐下角坐标和右上角坐标,不是题目描述的左上角和右下角坐标。
pps:这题跪了好久啊,差点要吐血了,总算还是过了。。。
ppps:还是太弱了啊。。。
详情请见代码:
#include <iostream> #include<cstdio> #include<cstring> #include<algorithm> using namespace std; const int N = 1005; double hash[N<<2]; struct node { double x,up,down; int flag; }line[N<<2]; struct nd { int flag; double one;//当前区间恰好覆盖一次的长度
double len;//当前区间覆盖2+次的长度,有效长度 }tree[N<<4]; int cmp(struct node a,struct node b) { return a.x < b.x; } void build(int num,int s,int e) { tree[num].flag = 0; tree[num].len = tree[num].one = 0; if(s == e) return; int mid = (s + e)>>1; build(num<<1,s,mid); build(num<<1|1,mid + 1,e); } void cal_len(int num,int s,int e)//计算当前区间长度 { if(tree[num].flag > 1)//当前区间被覆盖2+次,整个区间全部有效 { tree[num].len = hash[e + 1] - hash[s]; tree[num].one = 0; return; } else { if(tree[num].flag == 1)//当前区间恰好被覆盖一次 { if(s == e) tree[num].len = 0; else tree[num].len = tree[num<<1].len + tree[num<<1|1].len + tree[num<<1].one + tree[num<<1|1].one;//有效长度是子区间有效长度+子区间已经被恰好覆盖一次的长度 tree[num].one = hash[e + 1] - hash[s] - tree[num].len;//因为该区间被恰好覆盖了一次,所以当前区间被覆盖至少1+次,那么当前区间恰好被覆盖一次的就这么多了。 } else//当前区间没有被完整的覆盖过,那么有效区间就是子区间有效区间 { if(s == e) tree[num].len = tree[num].one = 0; else { tree[num].one = tree[num<<1].one + tree[num<<1|1].one; tree[num].len = tree[num<<1].len + tree[num<<1|1].len; } } } } void insert(int num,int s,int e,int l,int r,int add) { if(s == l && e == r) { tree[num].flag += add; cal_len(num,s,e); return; } int mid = (s + e)>>1; if(r <= mid) insert(num<<1,s,mid,l,r,add); else { if(l > mid) insert(num<<1|1,mid + 1,e,l,r,add); else { insert(num<<1,s,mid,l,mid,add); insert(num<<1|1,mid + 1,e,mid + 1,r,add); } } cal_len(num,s,e); } int getind(double x,int len) { int l,r,mid; l = 1; r = len; while(l <= r) { mid = (l + r)>>1; if(hash[mid] == x) return mid; else { if(hash[mid] > x) r = mid - 1; else l = mid + 1; } } return -1; } void print(int num,int s,int e) { printf("num:%d %d %lf %lf\n",num,tree[num].flag,tree[num].one,tree[num].len); if(s == e) { return; } int mid = (s + e)>>1; print(num<<1,s,mid); print(num<<1|1,mid + 1,e); } int main() { double x1,x2,y2,y1; int i,t,n,m; scanf("%d",&t); while(t --) { scanf("%d",&n); int y = 1; for(i = 1;i < n + n;i += 2) { scanf("%lf%lf%lf%lf",&x1,&y1,&x2,&y2); line[i].x = x1; line[i].up = y2; line[i].down = y1; line[i].flag = 1; line[i + 1].x = x2; line[i + 1].up = y2; line[i + 1].down = y1; line[i + 1].flag = -1; hash[y ++] = y1; hash[y ++] = y2; } sort(hash + 1,hash + y); m = 2; for(i = 2;i < y;i ++) if(hash[i] != hash[i - 1]) hash[m ++] = hash[i]; m --; build(1,1,m); sort(line + 1,line + n + n + 1,cmp); double ans = 0; int l,r; l = getind(line[1].down,m); r = getind(line[1].up,m); insert(1,1,m,l,r - 1,line[1].flag); for(i = 2;i <= n + n;i ++) { ans += (line[i].x - line[i - 1].x) * tree[1].len; l = getind(line[i].down,m); r = getind(line[i].up,m); insert(1,1,m,l,r - 1,line[i].flag); // print(1,1,m); } printf("%.2lf\n",ans); } return 0; } //359MS 500K