LOJ 3043: 洛谷 P5280: 「ZJOI2019」線段樹

題目傳送門:LOJ #3043ui

題意簡述:

你須要模擬線段樹的懶標記過程。spa

初始時有一棵什麼標記都沒有的 $n$ 階線段樹。code

每次修改會把當前全部的線段樹複製一份,而後對於這些線段樹實行一次區間修改操做。blog

即每次修改後線段樹棵數翻倍,第 $i$ 次修改後,線段樹共有 $2^i$ 棵。遞歸

區間修改操做的僞代碼以下:get

和我平常寫的遞歸式線段樹徹底一致。io

每次詢問你這些線段樹中有懶標記的節點總數。class

修改和詢問的總個數爲 $q$,$1\le n,q\le 10^5$。方法

題解:

靈感來源自 Sooke 的題解。im

考察一次區間修改操做會影響到的節點,共有 $5$ 類:

  1. 與修改區間相交,但不包含在修改區間內部的節點(淺紫色)。

  2. 包含在修改區間內部,但其父親不存在或不包含在修改區間內部(淺藍色)。

  3. 與修改區間相離,但其父親與修改區間相交(淺橙色)。

  4. 包含在修改區間內部,且其父親也包含在修改區間內部(深藍色)。

  5. 與修改區間相離,且其父親也與修改區間相離(深橙色)。

將節點分爲這 $5$ 類並非沒有根據的,能夠發現:

若僞代碼運行到了第 $17$ 行,則訪問到的是第 $1$ 類節點。
若僞代碼運行到了第 $14$ 行,則訪問到的是第 $2$ 類節點。
若僞代碼運行到了第 $11$ 行,則訪問到的是第 $3$ 類節點。
而第 $4,5$ 類節點分別是第 $2,3$ 類節點的子孫。

根據線段樹的複雜度,第 $1,2,3$ 類節點的個數均爲 $\mathcal{O}(\log n)$,而第 $4,5$ 類節點的個數爲 $\mathcal{O}(n)$。

接下來分析每次操做時受到影響的節點:

對於第 $1$ 類節點,操做後它們必然無懶標記。
對於第 $2$ 類節點,操做後它們必然有懶標記。
對於第 $3$ 類節點,操做後它們有無懶標記取決於操做前這個節點到根的鏈上有無懶標記。
對於第 $4,5$ 類節點,操做後它們不受影響。

咱們考慮維護每次操做後每一個節點 $u$ 有懶標記的樹的佔比,即在 $2^i$ 棵樹中,節點 $u$ 有懶標記的樹的比值,記做 $\mathrm{f}[u]$。
同時,由於第 $3$ 類節點須要額外信息,維護每次操做後每一個節點 $u$ 到根的路徑上有懶標記的樹的佔比,記做 $\mathrm{g}[u]$。

接下來咱們考慮一次操做後,每一個節點的信息如何更新,注意到同類節點的更新方式是相同的:

對於第 $1$ 類節點,一半保持原樣,另外一半無標記,因此 $\left\langle\mathrm{f}[u],\mathrm{g}[u]\right\rangle=\left\langle\frac{1}{2}\mathrm{f}[u],\frac{1}{2}\mathrm{g}[u]\right\rangle$。

對於第 $2$ 類節點,一半保持原樣,另外一半有標記,因此 $\left\langle\mathrm{f}[u],\mathrm{g}[u]\right\rangle=\left\langle\frac{1}{2}\mathrm{f}[u]+\frac{1}{2},\frac{1}{2}\mathrm{g}[u]+\frac{1}{2}\right\rangle$。

對於第 $3$ 類節點,一半保持原樣,另外一半的標記取決於 $u$ 到根的路徑,因此 $\left\langle\mathrm{f}[u],\mathrm{g}[u]\right\rangle=\left\langle\frac{1}{2}(\mathrm{f}[u]+\mathrm{g}[u]),\mathrm{g}[u]\right\rangle$。

對於第 $4$ 類節點,一半保持原樣,另外一半標記不受影響,但到根的路徑上必定有標記,因此 $\left\langle\mathrm{f}[u],\mathrm{g}[u]\right\rangle=\left\langle\mathrm{f}[u],\frac{1}{2}\mathrm{g}[u]+\frac{1}{2}\right\rangle$。

對於第 $5$ 類節點,一半保持原樣,另外一半標記不受影響,到根的路徑上的標記也不受影響,因此 $\left\langle\mathrm{f}[u],\mathrm{g}[u]\right\rangle=\left\langle\mathrm{f}[u],\mathrm{g}[u]\right\rangle$。

第 $5$ 類節點的信息沒有更改,第 $4$ 類節點僅有 $\mathrm{g}$ 有更改,由於第 $4$ 類節點有 $\mathcal{O}(n)$ 個,因此必須採用打懶標記的方法來維護。

而對於前 $3$ 類,直接維護便可。

再多維護一個 $\mathrm{Sf}[u]$ 表示 $u$ 的子樹中 $\mathrm{f}[v]$ 值之和便可統計答案。

#include <cstdio>

typedef long long LL;
const int Mod = 998244353;
const int Inv2 = 499122177;
const int MS = 1 << 18;

inline int Add(int x, int y) {
	return (x += y) >= Mod ? x - Mod : x;
}

int N, M;

int f[MS], g[MS], Sf[MS], T[MS];
inline void P(int i, int x) {
	g[i] = ((LL)g[i] * x + 1 - x + Mod) % Mod;
	T[i] = (LL)T[i] * x % Mod;
}
inline void Upd(int i, int Ty) {
	if (Ty) Sf[i] = f[i];
	else Sf[i] = Add(f[i], Add(Sf[i << 1], Sf[i << 1 | 1]));
}
inline void Psd(int i) {
	P(i << 1, T[i]);
	P(i << 1 | 1, T[i]);
	T[i] = 1;
}
void Build(int i, int l, int r) {
	T[i] = 1;
	if (l != r) {
		Build(i << 1, l, (l + r) >> 1);
		Build(i << 1 | 1, ((l + r) >> 1) + 1, r);
	}
}
void Mdf(int i, int l, int r, int a, int b) {
	if (r < a || b < l) {
		f[i] = (LL)(f[i] + g[i]) * Inv2 % Mod;
		Upd(i, l == r);
		return ;
	}
	if (a <= l && r <= b) {
		f[i] = (LL)(f[i] + 1) * Inv2 % Mod;
		Upd(i, l == r);
		P(i, Inv2);
		return ;
	}
	Psd(i);
	f[i] = (LL)f[i] * Inv2 % Mod;
	g[i] = (LL)g[i] * Inv2 % Mod;
	Mdf(i << 1, l, (l + r) >> 1, a, b);
	Mdf(i << 1 | 1, ((l + r) >> 1) + 1, r, a, b);
	Upd(i, 0);
}

int main() {
	scanf("%d%d", &N, &M);
	Build(1, 1, N);
	for (int m = 1, C = 1; m <= M; ++m) {
		int op, l, r;
		scanf("%d", &op);
		if (op == 1) {
			scanf("%d%d", &l, &r);
			Mdf(1, 1, N, l, r);
			C = Add(C, C);
		}
		else printf("%lld\n", (LL)C * Sf[1] % Mod);
	}
	return 0;
}
相關文章
相關標籤/搜索