线段树专辑—— hdu 1542 Atlantis

http://acm.hdu.edu.cn/showproblem.php?pid=1542

嗯哼,要开始利用线段树求解矩形面积的并、交、以及周长了。这题是求面积并的

有两种方法,一种思想很简单,操作方便,理解容易,但效率不高。先看一下吧

给定一个矩形的左下角坐标和右上角坐标分别为:(x1,y1)、(x2,y2),对这样的一个矩形,我们构造两条线段,一条定位在x1,它在y坐标的区间是[y1,y2],并且给定一个cover域值为1;另一条线段定位在x2,区间一样是[y1,y2],给定它一个cover值为-1。根据这样的方法对每个矩形都构造两个线段,最后将所有的线段根据所定位的x从左到右进行排序。

上图中,红色的字体表示的是该线段的cover值。刚刚开始的时候,线段树上的cover值都为0,但第一根线段(x==0)插入线段树的之后,我们将线段树上的cover加上该线段的cover,那么,此时线段树上被该线段覆盖的位置上的cover的值就为1,下次再插入第二根线段(x==1)此时发现该线段所覆盖的区间内,有一部分线段树的cover为0,另有一部分为1,仔细观察,但插入第二个线段的时候,如果线段树上cover已经为1的那些区间,和现在要插入的第二根线段之间,是不是构成了并面积?还不明白?看下图,绿色部分即为插入第二根线段后得到的并面积

够清楚了吧!也就是说,我们插入某跟线段的时候,只要看该线段所在区间上的cover是否大于等于1,如果是,那么就可以将并面积值加上(目前线段的x定位 - 上一线段的x定位)*(该区间的大小)

View Code
  1 #include <iostream>
2 #include <algorithm>
3 using namespace std;
4 const int maxn=110;
5
6 struct LINE
7 {
8 double x, y_down, y_up;
9 int flag;
10 bool operator<(const LINE &a)const
11 {
12 return x<a.x;
13 }
14 }line[2*maxn];
15
16 struct TREE
17 {
18 double y_down, y_up;
19 double x;
20 int cover; //用以表示加进线段树中的线段次数
21 bool flag; //此标记用来表示是否有超元线段;为了处理方便加上去的
22 }tree[1000*maxn];
23
24 int n;
25 double x1, y1, x2, y2;
26 int index=0;
27 double y[2*maxn];
28
29 void build(int i, int l, int r)
30 {
31 tree[i].x = -1; //-1表示该区间已经没有线段
32 tree[i].cover = 0; //表示该区间上有多少条线段;左边线段加进去则++,右边线段加进去则--
33 tree[i].y_down = y[l];
34 tree[i].y_up = y[r];
35 tree[i].flag = false;
36 if(l+1==r)
37 {
38 tree[i].flag = true; //flag==true表示达到了叶子节点
39 return;
40 }
41 int mid=(l+r)>>1;
42 build(2*i, l, mid);
43 build(2*i+1, mid, r);
44 }
45
46 double insert(int i, double x, double l, double r, int flag) //flag表示为左边还是右边
47 {
48 if (r<=tree[i].y_down || l>=tree[i].y_up)
49 return 0;
50 if (tree[i].flag)
51 {
52 if (tree[i].cover > 0) //递归到了叶子节点
53 {
54 double temp_x = tree[i].x;
55 double ans=(x-temp_x)*(tree[i].y_up - tree[i].y_down);
56 tree[i].x = x; //定位上一次的x
57 tree[i].cover += flag;
58 return ans;
59 }
60 else
61 {
62 tree[i].cover += flag;
63 tree[i].x = x;
64 return 0;
65 }
66 }
67 double ans1, ans2;
68 ans1 = insert(2*i, x, l, r, flag);
69 ans2 = insert(2*i+1, x, l, r, flag);
70 return ans1+ans2;
71 }
72
73 int main( )
74 {
75 // freopen("d:\\in.txt","r",stdin);
76 int count=0;
77 while (scanf("%d", &n)!=EOF&&n)
78 {
79 index = 1;
80 for (int i=1; i<=n; i++)
81 {
82 scanf("%lf%lf%lf%lf", &x1, &y1, &x2, &y2);
83 y[index] = y1;
84 line[index].x = x1;
85 line[index].y_down = y1;
86 line[index].y_up = y2;
87 line[index].flag = 1; //1表示左边
88
89 index++;
90 y[index] = y2;
91 line[index].x = x2;
92 line[index].y_down = y1;
93 line[index].y_up = y2;
94 line[index].flag = -1; //-1表示右边
95 index++;
96 }
97 sort(&y[1], &y[index]); //把所有的纵坐标按从小到大排序,把1写成了0,WA一次
98 sort(&line[1], &line[index]);
99 build(1, 1, index-1);
100 double ans=0;
101 for (i=1;i<index; i++)
102 {
103 ans += insert(1, line[i].x, line[i].y_down, line[i].y_up, line[i].flag);
104 }
105 printf("Test case #%d\nTotal explored area: %.2f\n\n", ++count, ans);
106 }
107 return 0;
108 }

 

强烈建议学习第二种方法,通常第一种方法能够的题目,第二种方法都是秒杀!

该方法同样需要在线段树中定义一个cover域,表示该线段区间目前被覆盖的线段数目。另外再加一个len域,表示该区间可用于与下一线段求并面积的y坐标区间长度。然后利用简单的dp,将所有信息集中于tree[1].len上,这样便不用想第一种方法那样每次都求到叶子线段,大大节约了时间,并且代码也少了很多!

View Code
  1 #include<iostream>
2 #include<string>
3 #include<algorithm>
4 using namespace std;
5
6 struct node
7 {
8 int l;
9 int r;
10 int cover;
11 double len;
12 };
13
14 node tree[2000];
15 double yy[250];
16 int n,len;
17
18 struct Line
19 {
20 double y_down;
21 double y_up;
22 double x;
23 int cover;
24 };
25
26 Line line[250];
27
28 int cmp(Line a,Line b)
29 {
30 return a.x<b.x;
31 }
32
33 int find(double x)
34 {
35 int l=0,r=len,mid;
36 while(l<=r)
37 {
38 mid=(l+r)/2;
39 if(yy[mid]==x)
40 return mid;
41 if(yy[mid]<x)
42 l=mid+1;
43 else
44 r=mid-1;
45 }
46 return l;
47 }
48
49 void build(int i,int l,int r)
50 {
51 tree[i].l=l;
52 tree[i].r=r;
53 tree[i].cover=0;
54 tree[i].len=0;
55 if(l+1==r)
56 return;
57 int mid=(l+r)/2;
58 build(2*i,l,mid);
59 build(2*i+1,mid,r);
60 }
61
62 void fun(int i)
63 {
64 if(tree[i].cover)
65 tree[i].len=yy[tree[i].r]-yy[tree[i].l]; //如果cover大于1,那么整段都可用于与下一线段求并面积
66 else if(tree[i].l+1==tree[i].r) //叶子线段
67 tree[i].len=0;
68 else
69 tree[i].len=tree[2*i].len+tree[2*i+1].len; //很简单的dp
70 }
71
72 void updata(int i,int l,int r,int cover)
73 {
74 if(tree[i].l>r || tree[i].r<l)
75 return;
76 if(tree[i].l>=l && tree[i].r<=r)
77 {
78 tree[i].cover+=cover;
79 fun(i);
80 return;
81 }
82 updata(2*i,l,r,cover);
83 updata(2*i+1,l,r,cover);
84 fun(i);
85 }
86
87 int main()
88 {
89 double x1,y1,x2,y2;
90 int i,m,a,b,cas=1;
91 freopen("in.txt","r",stdin);
92 while(scanf("%d",&n)==1 && n)
93 {
94 m=0;
95 for(i=0;i<n;i++)
96 {
97 scanf("%lf%lf%lf%lf",&x1,&y1,&x2,&y2);
98 yy[m]=y1;
99 line[m].cover=1;
100 line[m].x=x1;
101 line[m].y_down=y1;
102 line[m++].y_up=y2;
103
104 yy[m]=y2;
105 line[m].cover=-1;
106 line[m].x=x2;
107 line[m].y_down=y1;
108 line[m++].y_up=y2;
109 }
110 sort(yy,yy+m);
111 len=1;
112 for(i=1;i<m;i++)
113 {
114 if(yy[i-1]!=yy[i])
115 yy[len++]=yy[i];
116 }
117 len--;
118 build(1,0,len);
119 sort(line,line+m,cmp);
120 double ans=0;
121 printf("Test case #%d\n",cas++);
122 for(i=0;i<m-1;i++)
123 {
124 a=find(line[i].y_down);
125 b=find(line[i].y_up);
126 updata(1,a,b,line[i].cover);
127 ans+=tree[1].len*(line[i+1].x-line[i].x); //tree[1].len已经保留了整个树与line[i+1]所能求并面积的长度
128 }
129 printf("Total explored area: %0.2lf\n\n",ans);
130 }
131 return 0;
132 }




posted @ 2011-11-13 10:12  Accept  阅读(6442)  评论(10编辑  收藏  举报