参考:http://www.cnblogs.com/scau20110726/archive/2013/04/12/3016765.html
分析:
1.矩形比较多,坐标也很大,所以横坐标需要离散化(纵坐标不需要),熟悉离散化后这个步骤不难,所以这里不详细讲解了,不明白的还请百度
2.重点:扫描线法:假想有一条扫描线,从左往右(从右往左),或者从下往上(从上往下)扫描过整个多边形(或者说畸形。。多个矩形叠加后的那个图形)。如果是竖直方向上扫描,则是离散化横坐标,如果是水平方向上扫描,则是离散化纵坐标。下面的分析都是离散化横坐标的,并且从下往上扫描的。
扫描之前还需要做一个工作,就是保存好所有矩形的上下边,并且按照它们所处的高度进行排序,另外如果是上边我们给他一个值-1,下边给他一个值1,我们用一个结构体来保存所有的上下边
struct segment
{
double l,r,h; //l,r表示这条上下边的左右坐标,h是这条边所处的高度
int f; //所赋的值,1或-1
}
接着扫描线从下往上扫描,每遇到一条上下边就停下来,将这条线段投影到总区间上(总区间就是整个多边形横跨的长度),这个投影对应的其实是个插入和删除线段操作。还记得给他们赋的值1或-1吗,下边是1,扫描到下边的话相当于往总区间插入一条线段,上边-1,扫描到上边相当于在总区间删除一条线段(如果说插入删除比较抽象,那么就直白说,扫描到下边,投影到总区间,对应的那一段的值都要增1,扫描到上边对应的那一段的值都要减1,如果总区间某一段的值为0,说明其实没有线段覆盖到它,为正数则有,那会不会为负数呢?是不可能的,可以自己思考一下)。
每扫描到一条上下边后并投影到总区间后,就判断总区间现在被覆盖的总长度,然后用下一条边的高度减去当前这条边的高度,乘上总区间被覆盖的长度,就能得到一块面积,并依此做下去,就能得到最后的面积
(这个过程其实一点都不难,只是看文字较难体会,建议纸上画图,一画即可明白,下面献上一图希望有帮组)
1.区间的并
上面已经说的很详细了,代码如下:
//Accepted 1542 15MS 316K 2539 B C++
#include <iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
#define MAXN 400
struct node1
{
double x1,x2,h;
int d;
bool operator<(const node1 &a) const{return h<a.h;}
}line[MAXN];
double g[MAXN];
/**线段树**/
struct node2
{
int left,right,cover;
double len;
}tree[MAXN*3];
void creat(int root,int left,int right)
{
tree[root].cover=0;
tree[root].len=0;
tree[root].left=left;
tree[root].right=right;
if(right==left+1)
return ;
int mid=(right+left)>>1;
creat(2*root,left,mid);
creat(2*root+1,mid,right);
}
void upmark(int root)
{
if(tree[root].cover)
{
tree[root].len=g[tree[root].right]-g[tree[root].left];
}
else if(tree[root].left+1==tree[root].right)
tree[root].len=0;
else
tree[root].len=tree[2*root].len+tree[2*root+1].len;
}
void updata(int root,int left,int right,int d)
{
if(tree[root].left>=left&&tree[root].right<=right)
{
tree[root].cover+=d;
upmark(root);
return;
}
if(tree[root].left>right||tree[root].right<left) return ;
updata(2*root,left,right,d);
updata(2*root+1,left,right,d);
upmark(root);
}
/**线段树**/
int X;
int find(double key)
{
int l=0,r=X+1,mid;
while(l<=r)
{
mid=(l+r)>>1;
if(g[mid]<key) l=mid+1;
else if(g[mid]>key) r=mid-1;
else return mid;
}
return 0;
}
int main()
{
int n;
int kase=1;
while(~scanf("%d",&n)&&n)
{
int num=0;
double x1,x2,y1,y2;
for(int i=0;i<n;i++)
{
scanf("%lf%lf%lf%lf",&x1,&y1,&x2,&y2);
line[num].x1=x1;
line[num].x2=x2;
line[num].h=y1;
line[num].d=1;
g[num]=x1;
num++;
line[num].x1=x1;
line[num].x2=x2;
line[num].h=y2;
line[num].d=-1;
g[num]=x2;
num++;
}
sort(g,g+num);
X=unique(g,g+num)-g;
X--;
sort(line,line+num);
creat(1,0,X);
double ans=0;
for(int i=0;i<num-1;i++)
{
int l=find(line[i].x1);
int r=find(line[i].x2);
updata(1,l,r,line[i].d);
ans+=tree[1].len*(line[i+1].h-line[i].h);
}
printf("Test case #%d\n",kase++);
printf("Total explored area: %0.2lf\n\n",ans);
}
return 0;
}
2.矩形的交
只需要注意下更新就可以了。其它基本与 区间的并一样;
1. 要有两个域:once(被覆盖一次的长度) , len (被覆盖两次以上的长度)
2. 更新时 分三种情况: 1.该区间被覆盖2次以上,那么直接算出整个区间长就行,和区间并一样。
2. 该区间只被覆盖一次 分两种情况: 1:是叶子节点:len=0,once就是整个区间长。
2:不是叶子节点:len=左右子树的被覆盖两次和一次的和。(子树被覆盖一次+该区间一次就有两次了,所以要加上子树被覆盖一次的。)once=整个区间-len
3. 该区间被覆盖 0 次 分两种情况:1.叶子节点:len = once = 0
2.不是叶子节点:len = 左右子树 len 之和。 once = 左右子树 once 之和。
参考别人的代码:值得学习。。
#include <queue>
#include <stack>
#include <math.h>
#include <stdio.h>
#include <stdlib.h>
#include <iostream>
#include <limits.h>
#include <string.h>
#include <string>
#include <algorithm>
#define MID(x,y) ( ( x + y ) >> 1 )
#define L(x) ( x << 1 )
#define R(x) ( x << 1 | 1 )
#define BUG puts("here!!!")
using namespace std;
const int MAX = 2010;
struct Tnode{int l,r,cover;double once,len;};
Tnode node[MAX<<2];
struct Sline{double x,y1,y2;int flag;};
Sline l[MAX];
double y[MAX];
void add_line(double x1,double y1,double x2,double y2,int &cnt)
{
l[cnt].x = x1; l[cnt].y1 = y1; l[cnt].y2 = y2;
l[cnt].flag = 1;
y[cnt++] = y1;
l[cnt].x = x2; l[cnt].y1 = y1; l[cnt].y2 = y2;
l[cnt].flag = -1;
y[cnt++] = y2;
}
bool cmp(Sline a,Sline b)
{
if( a.x == b.x ) return a.flag > b.flag;
return a.x < b.x;
}
void init()
{
memset(node,0,sizeof(node));
}
void Build(int t,int l,int r)
{
node[t].l = l; node[t].r = r;
node[t].cover = 0; node[t].once = 0.0;
if( node[t].l == node[t].r - 1 )
return ;
int mid = MID(l,r);
Build(L(t),l,mid);
Build(R(t),mid,r);
}
void Updata_len(int t) // 重要理解!!
{
if( node[t].cover >= 2 )
{
node[t].once = 0;
node[t].len = y[node[t].r] - y[node[t].l];
return ;
}
if( node[t].cover == 1 )
{
if( node[t].l == node[t].r - 1 )
node[t].len = 0;
else
node[t].len = node[R(t)].len + node[L(t)].len + node[R(t)].once + node[L(t)].once;
node[t].once = y[node[t].r] - y[node[t].l] - node[t].len;
return ;
}
if( node[t].cover == 0 )
{
if( node[t].l == node[t].r - 1 )
node[t].len = node[t].once = 0;
else
{
node[t].len = node[R(t)].len + node[L(t)].len;
node[t].once = node[R(t)].once + node[L(t)].once;
}
return ;
}
}
void Updata(int t, Sline p)
{
if( y[node[t].l] >= p.y1 && y[node[t].r] <= p.y2 )
{
node[t].cover += p.flag;
Updata_len(t);
return ;
}
if( node[t].l == node[t].r - 1 ) return ;
int mid = MID(node[t].l,node[t].r);
if( p.y1 <= y[mid] )
Updata(L(t),p);
if( p.y2 > y[mid] )
Updata(R(t),p);
Updata_len(t);
}
void solve(int n,int cnt)
{
init();
Build(1,0,cnt-1);
double ans = 0.0;
Updata(1,l[0]);
for(int i=1; i<n; i++)
{
ans += node[1].len*(l[i].x - l[i-1].x);
Updata(1,l[i]);
}
printf("%.2lf\n",ans);
}
int main()
{
int ncases,n;
double x1,x2,y1,y2;
scanf("%d",&ncases);
while( ncases-- )
{
scanf("%d",&n);
int cnt = 0;
for(int i=0; i<n; i++)
{
scanf("%lf%lf%lf%lf",&x1,&y1,&x2,&y2);
add_line(x1,y1,x2,y2,cnt);
}
sort(y,y+cnt);
sort(l,l+cnt,cmp);
int t = unique(y,y+cnt) - y;
solve(cnt,t);
}
return 0;
}
3.求覆盖周长
这里在 扫描线的同时 要加上两边的值。
有三个更新:
1. cover 依然和前面一样,遇到下边就+1,遇到上边就-1;
2. m 是覆盖的边长。
3. line 是该区间有多少条边被覆盖,用来加上两个侧边。
4。 lc ,rc 用来判断端点 是否被覆盖,覆盖了就要减去一条重复的边。
#include <iostream>
#include<cstdio>
#include<algorithm>
using namespace std;
#define MAXN 20000
struct node1
{
int x1,x2,d,h;
bool operator<(const node1 &a) const
{
if(h!=a.h) return h<a.h;
else return d>a.d;
}
}s[MAXN];
int line[MAXN];
int num;
void read_line()
{
int x1,x2,y1,y2;
scanf("%d%d%d%d",&x1,&y1,&x2,&y2);
s[num].x1=x1;
s[num].x2=x2;
s[num].h=y1;
s[num].d=1;
line[num]=x1;
num++;
s[num].x1=x1;
s[num].x2=x2;
s[num].h=y2;
s[num].d=-1;
line[num]=x2;
num++;
}
struct node2
{
int l,r,cover,m,line;
int lc,rc;
}tree[MAXN*3];
void creat(int rt,int l,int r)
{
tree[rt].cover=0;
tree[rt].line=0;
tree[rt].m=0;
tree[rt].l=l;
tree[rt].r=r;
if(l+1==r) return;
int mid=(l+r)>>1;
creat(rt<<1,l,mid);
creat(rt<<1|1,mid,r);
}
void up_m(int rt)
{
if(tree[rt].cover>0)
tree[rt].m=line[tree[rt].r]-line[tree[rt].l];
else if(tree[rt].l+1==tree[rt].r)
tree[rt].m=0;
else
tree[rt].m=tree[rt<<1].m+tree[rt<<1|1].m;
}
void up_line(int rt)
{
if(tree[rt].cover>0)
{
tree[rt].line=1;
tree[rt].lc=tree[rt].rc=1;
}
else if(tree[rt].l+1==tree[rt].r)
{
tree[rt].line=0;
tree[rt].lc=tree[rt].rc=0;
}
else
{
int left=rt<<1, right=left|1;
tree[rt].lc=tree[left].lc;
tree[rt].rc=tree[right].rc;
tree[rt].line=tree[left].line+tree[right].line - tree[left].rc*tree[right].lc;
}
}
void updata(int rt,int l,int r ,int d)
{
if(line[tree[rt].l]>=l && line[tree[rt].r]<=r)
tree[rt].cover+=d;
else if(tree[rt].l+1==tree[rt].r)
return;
else
{
int mid=(tree[rt].l+tree[rt].r)>>1;
if(line[mid]>=r) updata(rt<<1,l,r,d);
else if(line[mid]<=l) updata(rt<<1|1,l,r,d);
else
{
updata(rt<<1,l,line[mid],d);
updata(rt<<1|1,line[mid],r,d);
}
}
up_m(rt);
up_line(rt);
}
int main()
{
int n;
while(~scanf("%d",&n))
{
num=0;
for(int i=0;i<n;i++) read_line();
sort(s,s+num);
sort(line,line+num);
int X=unique(line,line+num)-line;
creat(1,0,X-1);
int ans=0;
int now_m=0,now_line=0;
for(int i=0;i<num;i++)
{
updata(1,s[i].x1,s[i].x2,s[i].d);
if(i>0) ans+=2*now_line*(s[i].h-s[i-1].h);
ans+=abs(tree[1].m-now_m);
now_m=tree[1].m;
now_line=tree[1].line;
}
printf("%d\n",ans);
}
return 0;
}