資料結構——李超線段樹 學習筆記

RainPPR發表於2024-07-21

資料結構——李超線段樹 學習筆記

維護直線

考慮線段樹維護區間最優線段。

  • 其中,最優線段指的是,在區間 \([l,r]\) 中,中點 \(mid\) 處最優的線段。

  • 我們稱一個線段在單點更優 / 最優,顯然,是指此處的函式值更大。

  • 我們下面稱一個線段在區間內更優 / 最優,是指在中點處的比較。

  • 我們稱一個線段在區間 / 單點嚴格更優,是指在該區間任何一處都更優。

插入直線

首先考慮在區間 \([l,r]\) 中插入線段:

  • 若該區間無線段,那麼直接讓他成為最優線段。

  • 如果已經有了,注意到我們不方便將一個區間下傳,因此標記永久化。

設新線段為 \(f\),當前的最優線段為 \(g\),考慮合併,

  • 我們欽定 \(f\) 在區間 \([l,r]\) 弱於 \(g\),如果不滿足那麼交換即可。
  1. 若在左右端點 \(f\) 都更弱,那麼 \(g\) 嚴格優於 \(f\)\(f\) 不可能成為答案,不需要下傳。

  2. 若在左端點 \(f\) 更優,因為 \(f\) 在中點更弱,因此左側一定存在分界點,遞迴左側。

  3. 若在右端點 \(f\) 更優,因為 \(f\) 在中點更弱,因此右側一定存在分界點,遞迴右側。

複雜度分析:

  • 因為兩直線最多隻有一個交點,因此左右最多遞迴一個。

  • 因此,時間複雜度為,單次 \(\mathcal O(n\log n)\)


為何不能欽定 \(f\) 強於 \(g\) 然後加入 \(f\) 捏?

注意到如果 \(f\) 嚴格強於 \(g\),那麼為了更新答案,我們還是需要交換 \(f,g\)

然後這樣這個問題相當於沒有。

查詢最值

標記永久化之後,我們需要把從根到葉子節點的每一個最優線段計算。

注意到是區最值,是可以重複的,那麼我們隨便搞就可以了。

時間複雜度同線段樹,為單次 \(\mathcal O(\log n)\)

程式碼

P4254 [JSOI2008] Blue Mary 開公司

struct line {
	double k, b;
} p[M];

int tot;

double calc(int u, int t) {
	return p[u].b + p[u].k * t;
}

#define ls(k) ((k) << 1)
#define rs(k) ((k) << 1 | 1)

int best[N << 2];

void modify(int k, int l, int r, int u) {
	int &v = best[k];
	int mid = (l + r) >> 1;
	if (calc(u, mid) > calc(v, mid)) swap(u, v);
	if (calc(u, l) > calc(v, l)) modify(ls(k), l, mid, u);
	if (calc(u, r) > calc(v, r)) modify(rs(k), mid + 1, r, u);
}

double query(int k, int l, int r, int t) {
	double res = calc(best[k], t);
	if (l == r) return res;
	int mid = (l + r) >> 1;
	if (t <= mid) res = max(res, query(ls(k), l, mid, t));
	else res = max(res, query(rs(k), mid + 1, r, t));
	return res;
}

void Insert(double k, double b) {
	p[++tot] = {k, b};
	modify(1, 1, (int)5e4, tot);
}

double Query(int t) {
	return query(1, 1, (int)5e4, t);
}

維護線段

插入線段

我們延續上面的思路,但是。

我們需要線段樹式的遍歷到每一個節點,才能更新最優線段。

  • 注意到線段樹會把區間分為 \(\mathcal O(\log n)\) 個區間,

  • 我們需要對每個區間進行 \(\mathcal O(\log n)\) 的更新,

  • 因此,總時間複雜度是 \(\mathcal O(\log^2n)\) 的。

查詢最值

和上面沒有變化。

程式碼

下面是和上面類似的程式碼,也很好寫。

struct line {
	double k, b;
} p[M];

int tot;

double calc(int u, int t) {
	return p[u].b + p[u].k * t;
}

#define ls(k) ((k) << 1)
#define rs(k) ((k) << 1 | 1)

int best[N << 2];

void update(int k, int l, int r, int u) {
	int &v = best[k];
	int mid = (l + r) >> 1;
	if (calc(u, mid) > calc(v, mid)) swap(u, v);
	if (calc(u, l) > calc(v, l)) update(ls(k), l, mid, u);
	if (calc(u, r) > calc(v, r)) update(rs(k), mid + 1, r, u);
}

void modify(int k, int l, int r, int p, int q, int u) {
	if (l >= p && r <= q) return update(k, l, r, u);
	int mid = (l + r) >> 1;
	if (q <= mid) return modify(ls(k), l, mid, p, q, u);
	if (p >= mid + 1) return modify(rs(k), mid + 1, r, p, q, u);
	modify(ls(k), l, mid, p, q, u), modify(rs(k), mid + 1, r, p, q, u);
}

double query(int k, int l, int r, int t) {
	double res = calc(best[k], t);
	if (l == r) return res;
	int mid = (l + r) >> 1;
	if (t <= mid) res = max(res, query(ls(k), l, mid, t));
	else res = max(res, query(rs(k), mid + 1, r, t));
	return res;
}

void Insert(double k, double b) {
	p[++tot] = {k, b};
	modify(1, 1, (int)5e4, tot);
}

double Query(int t) {
	return query(1, 1, (int)5e4, t);
}

P4097 【模板】李超線段樹 / [HEOI2013] Segment

這個題目強制線上,且需要輸出最優線段且編號最小,因此可能會被卡精度,

constexpr double eps = 1e-8;

inline int Cmp(double x, double y) {
	if (x - y > eps) return 1;
	if (y - x > eps) return -1;
	return 0;
}

inline pair<double, int> Max(const pair<double, int> &a,
							 const pair<double, int> &b) {
	int c = Cmp(a.first, b.first);
	if (c == 0) return a.second < b.second ? a : b;
	return c == 1 ? a : b;
}

相關文章