1. 程式人生 > 實用技巧 >【Luogu P5490】【模板】掃描線

【Luogu P5490】【模板】掃描線

題目大意:

\(n\) 個矩形的面積並。

正文:

本題計算面積並在掃描線中較為簡單。拋開離散化,我們著重講掃描線。

與上圖為例,思考怎麼計算它們的幾何並。

暴力

用一個數組來存資訊,覆蓋了就標 1,否則標 0。暴力既好想又好寫,但當座標一大,時空都會超。

容斥

用總面積減去重合面積。但只侷限於重合少的題。

分割圖形

將圖形重新分割成一個個規則的矩形,用線段樹維護一條掃描的線。

如上圖,每掃描到一段,該段面積就是直線上覆蓋的長度乘該段的寬度。

順著這個思路來,用一個四元組 \((x,y_1,y_2,flag)\) 記錄每一條豎線,表示豎線的座標及是否是左右邊界。

線段樹維護掃描線上被覆蓋的長度,每次修改後,更新被覆蓋長度(如下)。

void pushup(int x)
{
	if(t[x].cnt) t[x].len = disx[t[x].r + 1] - disx[t[x].l]; 
	else t[x].len = t[x * 2].len + t[x * 2 + 1].len;
}

對於線段樹的標記,這裡引入 李煜東的《演算法競賽》,在本題我們只關心整個掃描線(線段樹根節點)上被覆蓋的長度。四元組又成對出現,所以線段樹區間修改也是重複出現,這樣就沒必要下傳延時標記,而採用更加簡單的做法:線上段樹每個節點上另加維護該節點代表的區間被矩形覆蓋的長度 \(len\),該節點自身被覆蓋的次數 \(cnt\)。對於一個四元組 \((x,y_1,y_2,flag)\)

,在 \([val(y_1),val(y_2)-1]\) 上執行區間修改。該區間被線段樹劃分成 \(\log N\) 個節點,把這些節點的 \(cnt\) 都加 \(k\)

對於線段樹任意一個節點 \([l,r]\),若 \(cnt>0\),則 \(len\) 就等於兩子節點 \(len\) 之和。在一個節點 \(cnt\) 被修改,以及線段樹傳遞資訊時,我們都按照方法更新 \(len\) 值。根節點 \(len\) 值就是整個掃描線被覆蓋的長度。

程式碼:

const int N = 2000000 + 10;
int n;

struct SegmentTree
{
	int l, r;
	ll cnt, len;
}t[N << 2];
ll x[N], disx[N], y[N];

struct node
{
	ll xd, xu, y, flag;
}a[N << 2];

bool cmp(node a, node b)
{
	if(a.y == b.y) return a.flag > b.flag;
	return a.y < b.y;
}

void build(int x, int l, int r)
{
	t[x].l = l, t[x].r = r;
	if(l == r) return;
	int mid = (t[x].l + t[x].r) / 2;
	build(x * 2, l, mid);
	build(x * 2 + 1, mid + 1, r);
}

void pushup(int x)
{
	if(t[x].cnt) t[x].len = disx[t[x].r + 1] - disx[t[x].l]; 
	else t[x].len = t[x * 2].len + t[x * 2 + 1].len;
}

void change(int x, int l, int r, ll k)
{
	if(l <= t[x].l && t[x].r <= r)
	{
		t[x].cnt += k;
		pushup(x);
		return;
	}
	int mid = (t[x].l + t[x].r) / 2;
	if(l <= mid) change(x * 2, l, r, k);
	if(r > mid) change(x * 2 + 1, l, r, k);
	pushup(x);
}

ll ans;

int main()
{
	scanf("%d", &n);
	int m = n * 2;
	for (int i = 1; i <= n; i ++)
	{
		scanf("%lld%lld%lld%lld", &x[(i << 1) - 1], &y[(i << 1) - 1], &x[(i << 1)], &y[(i << 1)]);
		a[(i << 1) - 1].xd = a[(i << 1)].xd = x[(i << 1) - 1];
		a[(i << 1) - 1].xu = a[(i << 1)].xu = x[(i << 1)];
				
		a[(i << 1) - 1].y = y[(i << 1) - 1];
		a[(i << 1)].y = y[(i << 1)];
		a[(i << 1) - 1].flag = 1;
		a[(i << 1)].flag = -1;
	}
	sort(x + 1, x + 1 + m);
	m = unique(x + 1, x + 1 + m) - (x + 1);
	for (int i = 1; i <= n * 2; i++)
	{
		int p1 = lower_bound(x + 1, x + 1 + m, a[i].xd) - x,
			p2 = lower_bound(x + 1, x + 1 + m, a[i].xu) - x;
		disx[p1] = a[i].xd, disx[p2] = a[i].xu;
		a[i].xd = p1;
		a[i].xu = p2;
	}
	m = 2 * n;
	build(1, 1, m);
	sort(a + 1, a + 1 + m, cmp);
	for (int i = 1; i <= m; i++)
	{
		change(1, a[i].xd, a[i].xu - 1, a[i].flag);
		ans += t[1].len * (a[i + 1].y - a[i].y);
	}
	printf("%lld\n", ans);
    return 0;
}