掃描線——~~原理分析與代碼塊(指針線段樹)~~——從入門到放棄


這里是一級標題

分割線下面是我原准備發的博客,然而事實是:

大概一年以前 通過書籍理解了掃描線的基本原理
5-19 花一小時抄了oi-wiki的代碼,疑惑於長還是寬,由於把數組改成指針,過不了樣例
5-20 又花了一小時抄了一遍,改了幾個bug,知道了區間維護要求build[M,R],過不了樣例
5-21 花了三小時理解代碼、看其他的博客、重構代碼、寫博客梳理代碼、過了樣例,后全WA,手造樣例WA、再次修改原理、AC手造樣例,再次全WA,然后放棄

2021-5-20 && 2021-5-21,這兩個愛你愛你我愛你的日子就在一點也不可愛的掃描線中度過了,無果,尋病終,最后得出結論:我只適合背代碼。

所以代碼還是錯的,我還是不會。

哪個好心人給我講講原理和細節或者幫着看一下最終的代碼/kl/kl/kl/kl

QAQ

--------------------分割線---------------------

這里是正文的一級標題

OI-wiki上講的是線段樹維護長,給出的代碼卻是維護的寬,誤導了我好久。

我的做法是線段樹維護長,用p維護給出的所有橫向邊(也就是矩形的長)。

掃描線從下往上掃,每個矩形下面的長是這個矩形加入的線段,遇到就加入線段樹,上面的長是這個矩形退出的線段,遇到就退出線段樹,上/下用flg表示,其值為0/1那么加入和退出的操作就等價於+flg

把所有的線段處理好后就無所謂屬於哪個矩形了,也就是線段獨立存在了,這時候我們按縱坐標給其排序,從下往上逐條處理即可。

獲得排序后,此時每條線段在哪個縱坐標上也就沒用了,因為我們維護的是掃描線,是一條二維的線。

所有矩形的面積並也就被分為了一個個單獨橫條,橫條的寬度即為相鄰兩條線段之間的距離p[i] - p[i - 1]

核心操作就是用線段樹rot維護區間[min_x, max_x]上的線段長度,然后乘上長條寬度累計入答案。

線段

維護縱坐標在y上的一條線段[x1, x2]

bool cmp(line a, line b){
  if(a.y == b.y)  return a.flg < b.flg;//注意先出后進
  return a.y < b.y;
}

struct line{
  int y, x1, x2, flg;
}p[maxn * 2];

線段樹

節點維護的信息是[l, r]這個區間里當前存在的長度。

struct node{
  int l, r, lazy, sum;
  node *ls, *rs;
}Mem[maxn * 4], *pool = Mem;

離散化:

所有的橫坐標放進num[]里然后sort,用[1, n * 2]作為線段樹的定義域,根據num[]訪問到真正的坐標值,放進nodel和r作為真正的定義域,也就是維護的區間。

離散化就決定了在建樹的時候左右孩子的遞歸就要是這樣的:

M = L + R >> 1;
u->ls = build(L, M);
u->rs = build(M, R);//而不是build(M + 1, R)
u->pushup(); 

這樣才能保證num[M]num[M + 1]這段區間不被漏掉。

注意到這個lazy是說區間[num[l], num[r]]這里面的線段出現了幾次,因為這個區間可能被多條矩形的下邊都加入了線段樹,退出的時候每次只能退出一條,而只有當所有矩形的上邊都退出了時候,這段區間的線段才真正不存在了,不然就是一直要算入答案的。

事實上,lazy只在當前用到的線段樹的最下面一層節點(一定要是表示num[i]~num[i+1]這么基本的節點)才有用,其余節點是沒有出現次數這一說的,只用維護sum就好了。

所以非葉節點的lazy = 0, sum > 0的情況是合法的。

這也決定了pushdown函數是不必要的。注意我們用的是EqualRange()而不是InRange()也就是說懶標記在這里並不適用,所有的基本線段都要維護。

現在我們來看看upd(x1, x2, flg)的操作,這個操作的對象是線段。

inline bool EqualRange(const int L, const int R){
  return (L == l) && (r == R);
}
inline bool OutofRange(const int L, const int R){
  return (L >= r) || (l >= R);
}
inline void maketag(const int flg){
  lazy += flg;
  if(lazy <= 0)  sum = 0;
  if(lazy > 0)  sum = r - l;
 }
inline void pushup(){
		sum = 0;
		if(ls != NULL)  sum += ls->sum;
		if(rs != NULL)  sum += rs->sum;
}
inline void pushdown(){
		if(ls != NULL) {
			ls->maketag(lazy);
		}	
		if(rs != NULL) 	rs->maketag(lazy);
		lazy = 0;
}
inline void upd(const int x1, const int x2, const int flg){
	    if(EqualRange(x1, x2)){
	        maketag(flg);
	    }
	    else if(!OutofRange(x1, x2)){
		pushdown();
	        if(ls != NULL) if(x1 < ls->r)ls->upd(x1, min(x2, ls->r), flg);
	        if(rs != NULL) if(x2 > rs->l)rs->upd(max(x1, rs->l), x2, flg);
	        pushup();
	    }
}

現在來強調最最最最重要的一個bug點,我在這里卡了一個小時。是OutofRange(L, R)函數。這個是基本函數了,由於是區間,端點的重合並不真的是在區間里,所以要加上等號!!!
對:

inline bool OutofRange(const int L, const int R){
  return (L >= r) || (l >= R);
}

錯:

inline bool OutofRange(const int L, const int R){
  return (L > r) || (l > R);
}

把上面的int全改為longlong后仍舊WA的假code(TAT

#include<map>
#include<cmath>
#include<ctime>
#include<queue>
#include<stack>
#include<vector>
#include<cstdio>
#include<string>
#include<cstdlib>
#include<cstring>
#include<iostream>
#include<algorithm>
#include<functional>
#define ll long long 
#define mod 998244353
using namespace std;
int rd(){
	int res = 0, fl = 1;
   char c = getchar();
   while(!isdigit(c)){
       if(c == '-') fl = -1;
       c = getchar();
   }
   while(isdigit(c)){
      res = (res << 3) + (res << 1) + c - '0';
      c = getchar();
   }
   return res * fl;
}
namespace force{
int main(){
	return 0;
}
}
const int maxn = 100010;
struct line{
	ll y, x1, x2, flg;
}p[maxn * 2];
int n;
int num[maxn * 2];
ll ans;
struct Node{
	ll l, r, sum, lazy;
	Node *ls, *rs;
	inline bool EqualRange(const ll L, const ll R){
		return (L == l) && (r == R);
	}
	inline bool OutofRange(const ll L, const ll R){
		return (L >= r) || (l >= R);
	}
	inline void maketag(const ll flg){
	    lazy += flg;
	    if(lazy <= 0)  sum = 0;
	    if(lazy > 0)  sum = r - l;
	}
	inline void pushup(){
		sum = 0;
		if(ls != NULL)  sum += ls->sum;
		if(rs != NULL)  sum += rs->sum;
	}
	inline void pushdown(){
		if(ls != NULL) {
			ls->maketag(lazy);
		}	
		if(rs != NULL) 	rs->maketag(lazy);
		lazy = 0;
	}
	inline void upd(const ll x1, const ll x2, const ll flg){
	    if(EqualRange(x1, x2)){
	        maketag(flg);
	    }
	    else if(!OutofRange(x1, x2)){
			pushdown();
	        if(ls != NULL) if(x1 < ls->r)ls->upd(x1, min(x2, ls->r), flg);
	        if(rs != NULL) if(x2 > rs->l)rs->upd(max(x1, rs->l), x2, flg);
	        pushup();
	    }
//	    printf("[%d,%d], lazy=%d, sum=%d\n", l, r, lazy, sum);
	}
}Mem[maxn * 4], *pool = Mem;
Node* New(){
	return ++pool;
}
Node* build(const int L, const int R){
	Node *u = New();
	u->lazy = 0;
	u->l = num[L];
	u->r = num[R];
	if(R - L <= 1){
		u->sum = 0;
	}
	else{
		int M = L + R >> 1;
		u->ls = build(L, M);
		u->rs = build(M, R);
		u->pushup();
	}
	return u;
}
bool cmp(line a, line b){
	if(a.y == b.y)	return a.flg < b.flg;
	return a.y < b.y;
}
int main(){
	n = rd();
	int x1, x2, y1, y2;
	for(int i = 1; i <= n; ++i){
		x1 = rd(); y1 = rd();  x2 = rd(); y2 = rd();
		p[i].y = y1;
		p[i].x1 = x1;
		p[i].x2 = x2;
		p[i].flg = 1;
		p[i + n].y = y2;
		p[i + n].x1 = x1;
		p[i + n].x2 = x2;
		p[i + n].flg = -1;
		num[i] = x1;
		num[i + n] = x2;
	}
	sort(num + 1, num + 1 + 2 * n);
	sort(p + 1, p + 1 + 2 * n, cmp);
	Node *rot = build(1, 2 * n);
	rot->upd(p[1].x1, p[1].x2, p[1].flg);
//	printf("sum %d\n", rot->sum);
	for(int i = 2; i <= n * 2; ++i){
//		printf("line: %d %d\n", p[i].x1, p[i].x2);
		ans += (p[i].y - p[i - 1].y) * (rot->sum);
		rot->upd(p[i].x1, p[i].x2, p[i].flg);
//		printf("sum %d\n", rot->sum);
	}
	printf("%lld\n", ans);
	return 0;
}
/*
3
100 100 150 150
150 150 200 200
200 200 250 255
*/



免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM