bitset 相關最佳化

Otue發表於2024-03-06

bitset 基礎用法

operator []: 訪問其特定的一位。
operator ==/!=: 比較兩個 bitset 內容是否完全一樣。
operator &/&=/|/| =/^/^=/~: 進行按位與/或/異或/取反操作。
bitset 只能與 bitset 進行位運算,若要和整型進行位運算,要先將整型轉換為 bitset。
operator <>/<<=/>>=: 進行二進位制左移/右移。
operator <>: 流運算子,這意味著你可以透過 cin/cout 進行輸入輸出。
成員函式
count(): 返回 true 的數量。
size(): 返回 bitset 的大小。
test(pos): 它和 vector 中的 at() 的作用是一樣的,和 [] 運算子的區別就是越界檢查。
any(): 若存在某一位是 true 則返回 true,否則返回 false。
none(): 若所有位都是 false 則返回 true,否則返回 false。
all():C++11,若所有位都是 true 則返回 true,否則返回 false。
set(): 將整個 bitset 設定成 true。
set(pos, val = true): 將某一位設定成 true/false。
reset(): 將整個 bitset 設定成 false。
reset(pos): 將某一位設定成 false。相當於 set(pos, false)。
flip(): 翻轉每一位。(相當於異或一個全是 1 的 bitset)
flip(pos): 翻轉某一位。
to_string(): 返回轉換成的字串表達。
to_ullong():C++11,返回轉換成的 unsigned long long 表達。
_Find_first(): 返回 bitset 第一個 true 的下標,若沒有 true 則返回 bitset 的大小。
_Find_next(pos): 返回 pos 後面(下標嚴格大於 pos 的位置)第一個 true 的下標,
若 pos 後面沒有 true 則返回 bitset 的大小。

bitset 最佳化例題

CF633G

首先可以知道:可以將子樹透過 dfn 序轉化為序列。

有一個思路是:維護大約 \(200\) 個樹狀陣列來儲存每個質數在區間中出現次數。複雜度 \(O(200\times n\log n)\)。在某些網站上可以透過(什麼oj我不說)。

換一個思路,有一個限制:\(m\leq 1000\),這意味著我們完全可以將區間內開一個大小為 \(1000\) 的陣列代表這個數是否出現。可以用線段樹維護這類資訊, push_up 時只需要將左子樹維護的陣列和右子樹維護的陣列或起來。區間加上一個數 \(x\) 相當於平移 \(x\) 單位,並且超出 \(m\) 範圍的要迴圈移位。

複雜度 \(O(nm\log n)\)。肯定過不去。甚至還不如之前做法。

但是我們發現:我們陣列維護的全是 01 資訊,可以用 bitset 來代替。push_up 直接 tree[p].s = (tree[ls].s | tree[rs].s),區間加直接 tree[p].s = (((tree[p].s) >> (m - x)) | (tree[p].s << x))

複雜度 \(O(\dfrac{nm\log n}{32})\)。只放部分程式碼:

struct edge {
	int l, r, lazy;
	bitset<1005> s;
}tree[N * 4];
void push_up(int p) {
	tree[p].s = (tree[ls].s | tree[rs].s);
}
void down(int p, int x) {
	tree[p].lazy += x; tree[p].lazy %= m;
	tree[p].s = (((tree[p].s) >> (m - x)) | (tree[p].s << x));
}
void push_down(int p) {
	if (tree[p].lazy) {
		tree[p].lazy %= m;
		down(ls, tree[p].lazy); down(rs, tree[p].lazy);
		tree[p].lazy = 0;
	}
}

P5670

這題和上一道例題如出一轍,因為只看後 \(m\) 位,那前面的位其實就沒有用了!等價於模上 \(2^m\)。這道題維護異或資訊就行了。只放部分程式碼:

struct edge {
	int l, r, lazy;
	bitset<1024> s;
}tree[N * 4];

void push_up(int p) {
	tree[p].s = tree[ls].s ^ tree[rs].s;
}

void add(int p, int x) {
	tree[p].lazy = (tree[p].lazy + x) % m;
	tree[p].s = ((tree[p].s >> (1024 - x)) | (tree[p].s << x));
}

void push_down(int p) {
	if (tree[p].lazy) {
		add(ls, tree[p].lazy);
		add(rs, tree[p].lazy);
		tree[p].lazy = 0;
	}
}

signed main() {
	n = read(), m = read(), p = read(); m = (1 << m);
	for (int i = 1; i <= n; i++) a[i] = read();
	build(1, 1, n);
	while (p--) {
		int op, l, r, x;
		op = read(), l = read(), r = read();
		if (op == 1) {
			x = read();
			modify(1, l, r, x);
		} 
		else {
			ans.reset();
			query(1, l, r);
			int res = 0;
			for (int i = 0; i <= 1023; i++) if (ans[i]) res ^= i;
			wr(res & (m - 1));
			putchar('\n');
		}
	}
}

習題:

  • AT_abc258_g: [ABC258G] Triangle
  • P3674: 小清新人渣的本願
  • P4688: 掉進兔子洞
  • P5355: 由乃的玉米田