會遇到這種題:給你很多矩形,如果一個區域被多次覆蓋,只計算一次,問總面積.
一直說是線段樹,但是不知道是掃描線,寫篇博客記錄一下.
掃描線是這麼個意思
肯定需要離散化了,可以選擇離散化x軸或者y軸的點,然後就分割成了幾個區間,x1到x2,x2到x3,x3到x4,我們以xi代表以它爲起點,以下一個離散化的點爲終點的線段.
然後我們有四個高度,y1,y2,y3,y4,可以知道,面積等於(y2-y1)*(x3-x1)+.........這樣面積實際上就可控了.
那麼具體操作怎麼辦捏,以離散化x軸爲栗子,我們在輸入的時候可以找到一個上邊和下邊,我們把下邊標記爲1,上邊標記-1,
排序之後,y1,2,3,4順次排序,
現在開始處理:
這是第一次的情況,y1是下底邊,[x1,x3)被標記爲1,然後乘以這段的高.以HDU1542的樣例爲假設,
樣例:
2
10 10 20 20
15 15 25 25.5
0
則黃色部分就是(20-10)*(15-10) = 50;
第二次的情況:
現在我們掃描了y2,此時我們的對x2,x3進行了掃描,現在橫軸長度變爲[x1,x4),爲什麼捏,因爲之前的圖一,黃色部分覆蓋了[x1,x3),現在[x2,x3)被掃描兩次,[x1,x2)被掃描一次,所以,此時的面積是藍色部分加上和藍色部分平行的黃色部分,是(20-15(高))*(25-10(長)) = 75.
第三次掃描,是對y3進行掃描,之前都是下邊界,現在遇到上邊界-1的情況了.
下面的箭頭就是這個上邊界掃描完產生的變化,然後面積就是黃色部分加起來,再之後是y4的掃描,-1之後等於0,可掃可不掃.
//Atlantis HDU - 1542
#include<bits/stdc++.h>
using namespace std;
const int maxn = 505;
int lazy[maxn<<2];
int add[maxn<<2];
double x[maxn<<2],sum[maxn<<2];
struct EDGE
{
int ss;//上下邊
double l,r,h;//左右,高度
EDGE(double _x1=0,double _x2=0,double _y=0,int _up_or_down=0)
{
l = _x1;
r = _x2;
h = _y;
ss = _up_or_down;
}
bool operator<(const EDGE & a)
{
return h<a.h;
}
} dian[maxn];
void pushUP(int rt,int l,int r)
{
if(add[rt])//如果有標記,則長度爲標記的長度
sum[rt] = x[r+1]-x[l];
else if(l == r)//相等則爲長度0
sum[rt] = 0;
else sum[rt] = sum[rt<<1]+sum[rt<<1|1];//等於左邊+右邊的sum
}
void update(int rt,int L,int R,int val,int l,int r)//更新[l,r]區間
{
int mid;
if(L<=l && R >= r)
{
add[rt] += val;
pushUP(rt,l,r);
return;
}
mid = (l+r)>>1;
if(L<=mid)
update(rt<<1,L,R,val,l,mid);
if(R>mid)
update(rt<<1|1,L,R,val,mid+1,r);
pushUP(rt,l,r);
}
int main()
{
// freopen("in.txt","r",stdin);
ios::sync_with_stdio(false);
cin.tie(0),cout.tie(0);
int n;
int kase = 1;
double x1,y1,x2,y2,ans;
while(cin>>n && n)
{
ans = 0;
int top = 0,l,r;
printf("Test case #%d\n",kase++);
printf("Total explored area: ");
for(int i=0; i<n; i++)
{
cin>>x1>>y1>>x2>>y2;
x[top] = x1;
dian[top++] = EDGE(x1,x2,y1,1);//下邊界標記爲1
x[top] = x2;
dian[top++] = EDGE(x1,x2,y2,-1);//上邊界標記爲-1
}
sort(x,x+top);
sort(dian,dian+top);
int k = 1;
for(int i=1; i<top; i++)
{
if(x[i] != x[i-1])
x[k++] = x[i];//序列離散化變成[0,k);
}
memset(add,0,sizeof(add));
memset(sum,0,sizeof(sum));
for(int i=0; i<top-1; i++)
{
l = lower_bound(x,x+k,dian[i].l)-x;//下邊界
r = lower_bound(x,x+k,dian[i].r)-x-1;//上邊界,因爲每個點代表的是以當前點爲起點的下一段,所以要-1
update(1,l,r,dian[i].ss,0,k-1);//更新[0,k-1]區間
ans += (sum[1]*(dian[i+1].h-dian[i].h));
}
printf("%.2f\n\n",ans);
}
return 0;
}
然後開始解析代碼職能,sum需要統計的是add標記過的長度,add是標記現在當前段被幾個下邊界+1,我們的邊需要按照高度從小到大排序,ss表示+1和-1,sum[1]就是被標記的有效長度,然後由於區間是[)的,所以r lower_bound之後需要-1,
關於去重:據說可去可不去,意義:把x軸離散化之後變成有序的,相當於打個映射.二分的時候直接是嚴格單調遞增的,其他的沒有影響.
變形題:
Rectangles Gym - 101982F
當時打死都不會,還以爲是二維線段樹,其實好像沒這東西,全是自己瞎猜的.
現在要處理的問題比較狗了一點,還是那麼掃描,但是負負得正,正正得負,記得一個運算,^運算,所以我們現在+val應該改成^1;
//【掃描線】Gym - 101982 - F - Rectangles
#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
const int maxn = 1e5+5;
int lazy[maxn<<3];
int add[maxn<<3];
int x[maxn<<2],sum[maxn<<3];
struct EDGE
{
int ss;//上下邊
int l,r,h;//左右,高度
EDGE(int _x1=0,int _x2=0,int _y=0)
{
l = _x1;
r = _x2;
h = _y;
}
bool operator<(const EDGE & a)
{
return h<a.h;
}
} e[maxn<<2];
void cal(int rt,int l,int r)//長度裏面減去之前有標記的
{
sum[rt] = (x[r]-x[l])-sum[rt];
}
void pushUP(int rt)//向上更新正常求和
{
sum[rt] = sum[rt<<1]+sum[rt<<1|1];
}
void pushDown(int rt,int l,int r)//向下更新
{
if(!add[rt])return;
add[rt<<1] ^= 1;
add[rt<<1|1] ^= 1;
int mid = (l+r)>>1;
cal(rt<<1,l,mid);//計算sum
cal(rt<<1|1,mid,r);
add[rt] = 0;
}
void update(int rt,int L,int R,int l,int r)//更新[L,R]區間
{
int mid;
if(L<=l && R >= r)//如果rt包含的區間完全被覆蓋
{
add[rt] ^= 1;
cal(rt,l,r);
return;
}
pushDown(rt,l,r);//向下更新一次懶惰標記
mid = (l+r)>>1;
if(L<mid)
update(rt<<1,L,R,l,mid);
if(R>mid)
update(rt<<1|1,L,R,mid,r);
pushUP(rt);
}
int main()
{
int n;
scanf("%d",&n);
int x1,x2,y1,y2,all=0;
for(int i=1; i<=n; i++)
{
scanf("%d%d%d%d",&x1,&y1,&x2,&y2);
e[2*i-1].l=min(x1,x2);//l嚴格小於r
e[2*i-1].r=max(x1,x2);
e[2*i-1].h=min(y1,y2);
e[2*i].l=min(x1,x2);
e[2*i].r=max(x1,x2);
e[2*i].h=max(y1,y2);
x[2*i-1]=x1,x[2*i]=x2;
}
sort(x+1,x+1+n*2);
all=unique(x+1,x+1+n*2)-x-1;
for(int i=1; i<=2*n; i++)//處理出來l和r在離散化之後的x軸上的區間
e[i].l=lower_bound(x+1,x+1+all,e[i].l)-x,e[i].r=lower_bound(x+1,x+1+all,e[i].r)-x;
sort(e+1,e+1+2*n);
LL ans=0;
for(int i=1; i<2*n; i++)
{
update(1,e[i].l,e[i].r,1,all);//更新這條線段
ans+=(LL)sum[1]*(e[i+1].h-e[i].h);
}
printf("%lld\n",ans);
}