日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 人文社科 > 生活经验 >内容正文

生活经验

多个矩形,求覆盖面积,周长,及交点

發布時間:2023/11/27 生活经验 46 豆豆
生活随笔 收集整理的這篇文章主要介紹了 多个矩形,求覆盖面积,周长,及交点 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

問題:給出若干個矩形,(給的是矩形左上角和右下角坐標),求最后所得圖形的面積/周長;

三個矩形如左圖所示,而若要計算面積,看右圖,用3個矩形各自的面積之和減去重復部分(紅色和藍色)的面積

人算很簡單,但是用算法怎么實現呢?

此類問題一般都是用線段樹輔助掃描法來計算;

什么是掃描法?有什么用?怎么用?

可以想象成一根假想的線,將圖從左往右或從右往左或自下而上或自上而下“掃描”一遍,至于掃描的是什么則根據具體應用選擇。

掃描線可以計算矩形面積、周長,可以計算線段交點,可以實現多邊形掃描轉換,在圖的處理方面經常用到。

這里總結一下掃描線計算矩形面積和周長的算法。

怎么用?首先,對于之前的圖,除了用總面積減去重合面積,還可以換一種計算方法,如圖:













此圖用4條橫線將整個圖劃分成了5個部分,顯然此時再算面積就可以用各個顏色的部分求和。

想想,這樣計算的整個慢過程:

假設我們的視線自下而上,首先,我們看到了最下面灰色矩形的下邊,

用這個下邊的長度乘以這條邊和上一條邊的高度差即得到灰色矩形面積,

繼續看到藍色的矩形的下邊,雖然藍色矩形有兩個,但我們計算時自然會用結合律將兩個矩形的下邊加起來再去乘以同樣的高,

然后重復這樣的操作,我們最終可以求得整個圖形的面積。


但是,這依舊是人做的,計算機要怎么實現呢?


首先的問題是,計算機要怎么保存這張圖這些矩形?

從剛才的過程,我們不難發現,我們只需要保存這張圖里面的所有水平的邊即可。

對于每條邊,它所擁有的屬性是:這條邊的左右端點(的橫坐標),這條邊的高度(縱坐標),這條邊屬于矩形的上邊還是下邊(想想為什么保存這個屬性)

剛剛計算中我們遇到兩個藍色矩形的一部分一眼就能看出這兩個藍色矩形的‘寬’是多少,用計算機怎么做到?

線段樹華麗登場!

我們以整個圖最左邊的豎線作為區間左端點,最右邊的豎線作為區間右端點,去維護這個區間的有效長度(即被覆蓋的長度

比如掃到第2條邊的時候,有效長度就是兩個藍色矩形的寬之和。

這樣,我們用掃描線去掃描每一條邊的時候,都需要更新線段樹的有效長度

是如何更新的呢?

如果掃到的這條邊是某矩形的下邊,則往區間插入這條線段

如果掃到的這條邊是某矩形的上邊,則往區間刪除這條線段

為什么?自己試著模擬一下就不難發現:

因為我們是自下而上的掃這個圖,掃到下邊相當于剛剛進入一個矩形,掃到上邊則是要離開一個矩形

利用線段樹把每條邊的有效長度找到了,也就是找到了每部分的所有矩形的總寬,那么高呢?

高就簡單多了,對于所有的邊,按照高度從小到大排列,那么矩形高就是每相鄰邊之間的高度差

給個例子:HDU 1542 Atlantis

然后看看用代碼具體是怎么實現的:

ps: ? 特別說一下,關于上邊和下邊的標記,用-1標記下邊,1標記上邊是最合理的(想想為什么,提示:下邊--刪除,上邊--插入)

? ? ? ? 這題橫坐標略大,需要離散化處理

#define mem(a,x) memset(a,x,sizeof(a))
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<queue>
#include<set>
#include<stack>
#include<cmath>
#include<map>
#include<stdlib.h>
#include<cctype>
#include<string>
using namespace std;
typedef long long ll;
const int N = 111;
struct Edge
{double l,r;//這條線的左右端點的橫坐標double h;//這條線的縱坐標int f;//這條線是矩形的上邊還是下邊
}e[N<<1];
bool cmp(Edge a,Edge b)
{return a.h < b.h;
}
struct Node
{int l,r;//橫坐標的區間,是橫坐標數組的下標int s;//該節點被覆蓋的情況(是否完全覆蓋)double len;//該區間被覆蓋的總長度
}q[N*8];
double x[2*N];//橫坐標
#define ls i<<1
#define rs i<<1|1
#define m(i) ((q[i].l + q[i].r)>>1)
void build(int i,int l,int r)
{q[i].l = l,q[i].r = r;q[i].s = 0;q[i].len = 0;if (l == r) return;int mid = m(i);build(ls,l,mid);build(rs,mid+1,r);
}
void pushup(int i)
{if (q[i].s) //非零,已經被整段覆蓋{q[i].len = x[q[i].r+1] - x[q[i].l];}else if (q[i].l == q[i].r) //這是一個點而不是線段{q[i].len = 0;}else //是一條沒有整個區間被覆蓋的線段,合并左右子的信息{q[i].len = q[ls].len + q[rs].len;}
}
void update(int i,int l,int r,int xx)//這里深刻體會為什么令下邊為1,上邊-1
{                                   //下邊插入邊,上邊刪除邊if (q[i].l == l&&q[i].r == r){q[i].s += xx;pushup(i);//更新區間被覆蓋de總長度return;}int mid = m(i);if (r <= mid) update(ls,l,r,xx);else if (l > mid) update(rs,l,r,xx);else{update(ls,l,mid,xx);update(rs,mid+1,r,xx);}pushup(i);
}
int main()
{int n;int kas = 0;while (scanf("%d",&n) == 1&&n){int tot = 0;for (int i = 0;i < n;++i){double x1,x2,y1,y2;scanf("%lf %lf %lf %lf",&x1,&y1,&x2,&y2);//輸入一個矩形Edge &t1 = e[tot];Edge &t2 = e[1+tot];t1.l = t2.l = x1,t1.r = t2.r = x2;t1.h = y1;t1.f = 1;t2.h = y2;t2.f = -1;x[tot] = x1;x[tot+1] = x2;tot += 2;}sort(e,e+tot,cmp);//邊按高度從小到大排序(自下而上掃描)sort(x,x+tot);//離散化橫坐標int k = 1;for (int i = 1;i < tot;++i){if (x[i] != x[i-1]) //去重{x[k++] = x[i];}}build(1,0,k-1);//離散化后的區間是[0,k-1]double ans = 0.0;for (int i = 0;i < tot;++i){//因為線段樹維護的是橫坐標們的下標,所以對每條邊求出其兩個橫坐標對應的下標int l = lower_bound(x,x+k,e[i].l) - x;//在橫坐標數組里找到這條邊的位置int r = lower_bound(x,x+k,e[i].r) - x - 1;update(1,l,r,e[i].f);//每掃到一條邊就更新橫向的覆蓋lenans += (e[i+1].h - e[i].h)*q[1].len;//q[1]是整個區間,q[1].k=len是整個區間的有效長度//計算面積就是用區間橫向的有效長度乘以兩條邊的高度差(面積是兩條邊里面的部分)}printf("Test case #%d\n",++kas);printf("Total explored area: %.2f\n\n",ans);}return 0;
}


說完了矩形面積,矩形周長的方法自然是類似的,但是周長的計算卻更復雜些,看這張圖:













周長可以分成兩部分計算,橫線和豎線,如圖將所有彩色的橫線加起來就是橫向的所有長度了

然后可以采用豎直方向的掃描線將豎線的所有長度求出來

那么怎么計算橫線的長度呢?

橫線的長度 = 【現在這次總區間被覆蓋的程度和上一次總區間被覆蓋的長度之差的絕對值】

想想為什么要加絕對值(提示:下邊--刪除,上邊--插入)

這樣用自下而上和從左往右的兩次掃描即可得到答案。

但是,這樣的方法顯得有些笨,有沒有更高端的方法呢?

再看一張圖:













看出什么了嗎?這張圖在上面那張圖的基礎上多了幾條豎線。

我的意思是說,我們可以只做一次自下而上的掃描就把橫線豎線都算出來!

豎線的算法和上面說的方法一樣:【現在這次總區間被覆蓋的程度和上一次總區間被覆蓋的長度之差的絕對值】

豎線要怎么計算?

首先我們現在改一下線段樹保存的屬性,我們用如下信息記錄線段樹的節點:

1. l , r : 該節點代表的線段的左右端點坐標

2.len : 這個區間被覆蓋的長度(即計算時的有效長度)

3.s : 表示這個區間被覆蓋了幾次

4. lc , rc : 標記這個節點的左右兩個端點是否被覆蓋(0表示沒有,1表示有)

5.num :這個區間有多少條線段(這個區間被多少條線段覆蓋)

這里的num涉及到豎線的計算,故解釋一下,舉幾個例子:

? ?若區間[0,10]被[1,2][4,5]覆蓋,則num = 2

? ?若區間[0,10]被[1,3][4,5]覆蓋,則num = 1(兩區間剛好連在一起)

? ?若區間[0,10]被[1,5][2,6]覆蓋,則num = 1(兩區間連起來還是一段)

然后就可以計算豎線了:

豎線的長度 = 【下一條即將被掃到的橫線的高度 - 現在掃到的橫線的高度】*2*num

乘2是因為每條線段有兩個端點;

看上圖中棕色線段的豎線有4條,因為棕色的橫線由2條線段組成

白色線段的豎線只有2條,因為白色的橫線由1條線段組成(雖然這1條線段是由許多線段組合而成,但它依舊只算1條線段)

這樣,依舊只掃一次就可以算出周長。

給個例子:HDU 1828Picture

代碼實現:

#define mem(a,x) memset(a,x,sizeof(a))
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<queue>
#include<set>
#include<stack>
#include<cmath>
#include<map>
#include<stdlib.h>
#include<cctype>
#include<string>
using namespace std;
typedef long long ll;
const int N = 5007;
const int X = 20007;
const int inf = 1<<29;
struct Edge//掃描線
{int l,r;//左右端點的橫坐標int h;//這條線的高度,即縱坐標int f;//標記這條邊是上邊(-1)還是下邊(1)
}e[N*2];
bool cmp(Edge a,Edge b)
{return a.h < b.h;//高度從小到大排,掃描線自下而上掃
}
struct Node
{int l,r;//該節點代表的線段的左右端點坐標int len;//這個區間被覆蓋的長度int s;//表示這個區間被重復覆蓋了幾次bool lc,rc;//表示這個節點左右兩個端點是否被覆蓋(0表示沒有被覆蓋,1表示有被覆蓋)int num;//這個區間有多少條線段(這個區間被多少條線段覆蓋)//len用來計算橫線 num用來計算豎線
}q[4*X];
#define ls i<<1
#define rs i<<1|1
#define m(i) ((q[i].l + q[i].r)>>1)
void pushup(int i)//區間合并
{if (q[i].s)//整個區間被覆蓋{q[i].len = q[i].r - q[i].l + 1;q[i].lc = q[i].rc = 1;q[i].num = 1;}else if (q[i].l == q[i].r)//這是一個點而不是一條線段{q[i].len = 0;q[i].lc = q[i].rc = 0;q[i].num = 0;}else //是一條沒有整個區間被覆蓋的線段,合并左右子的信息{q[i].len = q[ls].len + q[rs].len ;//長度之和q[i].lc = q[ls].lc;q[i].rc = q[rs].rc;//和左兒子共左端點,和右兒子共右端點q[i].num = q[ls].num + q[rs].num  - (q[ls].rc&q[rs].lc);//如果左子的右端點和右子的左端點都被覆蓋了}
}
void build (int i,int l,int r)
{q[i].l = l,q[i].r = r;q[i].s = q[i].len = 0;q[i].lc = q[i].rc = q[i].num = 0;if (l == r) return;int mid = m(i);build(ls,l,mid);build(rs,mid+1,r);
}
void update(int i,int l,int r,int xx)
{if (l == q[i].l && q[i].r == r){q[i].s += xx;pushup(i);return;}int mid = m(i);if (r <= mid) update(ls,l,r,xx);else if (l > mid) update(rs,l,r,xx);else{update(ls,l,mid,xx);update(rs,mid+1,r,xx);}pushup(i);
}
int main()
{int n;while (cin>>n){int x1,x2,y1,y2,mx = -inf,mn = inf;int tot = 0;for (int i = 0;i < n;++i){scanf("%d %d %d %d",&x1,&y1,&x2,&y2);mx = max(mx,max(x1,x2));mn = min(mn,min(x1,x2));Edge & t1 = e[tot];Edge & t2 = e[tot+1];t1.l = t2.l = x1,t1.r = t2.r = x2;t1.h = y1;t1.f = 1;t2.h = y2;t2.f = -1;tot += 2;}sort(e,e+tot,cmp);//數據小可以不離散化int ans = 0;//計算周長int last = 0;//保存上一次的總區間的被覆蓋的長度build(1,mn,mx-1);//每兩條橫線之間才會有豎線for (int i = 0;i < tot;++i){update(1,e[i].l,e[i].r-1,e[i].f);//根據掃描線更新//計算周長//橫線:現在這次總區間被覆蓋的程度和上一次總區間被覆蓋的長度之差的絕對值ans += abs(q[1].len - last);//豎線:[下一條橫線的高度-現在這條橫線的高度]*2*numans += (e[i+1].h - e[i].h)*2*q[1].num;last = q[1].len;//每次都要更新上一次總區間覆蓋的長度}printf("%d\n",ans);}return 0;
}

?

進階:

?HDU 1255 覆蓋的面積(線段樹+掃描線求面積【升級版】)

轉載于:https://www.cnblogs.com/bytebull/p/5973485.html

總結

以上是生活随笔為你收集整理的多个矩形,求覆盖面积,周长,及交点的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。