「NOI Online 2021 #1」島嶼探險


「NOI Online 2021 #1」島嶼探險

題目大意

給定一排 \(n\) 個元素,每個元素有兩個屬性:\(a_i, b_i\)

\(q\) 次詢問,每次詢問給出四個參數 \(l_j, r_j, c_j, d_j\)。問區間 \([l, r]\) 里滿足 \(a_i\operatorname{xor} c_j \leq \min\{b_i, d_j\}\)\(i\) 有多少個。

數據范圍:\(1\leq n,q\leq 10^5\)\(1\leq l_j\leq r_j\leq n\)\(0\leq a_i, b_i, c_j, d_j\leq 2^{24} - 1\)

本題題解

0. 約定

\(m = \max\{a_i, b_i, c_j, d_j\}\),這是分析復雜度用的。


1. 分析一次詢問

對於一次詢問 \(l_j, r_j, c_j, d_j\),考慮把 \(i\) 分為 \(b_i > d_j\)\(b_i\leq d_j\) 兩類,分別統計答案。

1.1. b[i] > d[j] 的 i

對於 \(b_i > d_j\)\(i\),答案是 \(\sum_{i} [a_i \operatorname{xor} c_j\leq d_j]\)。此時的特點是:答案與 \(b_i\) 無關。考慮把這些 \(a_i\) 插入到一個 \(\text{01 trie}\) 中:也就是按二進制從高位到低位的順序,把 \(a_i\) 當成一個 \(01\) 串。我們在 \(\text{01 trie}\) 上從根往下走(也就是按二進制從高位向低位走),對於當前節點,假設它深度為 \(h\),那么它代表的值等於 \(c_j\operatorname{xor}d_j\) 的前 \(h\) 高位。考慮下一位:

  • 如果 \(d_j\) 的下一位為 \(0\),那么說明 \(a_i\) 的下一位必須和 \(c_j\) 的下一位相等(否則 \(a_i \operatorname{xor} c_j > d_j\),不滿足要求),我們直接向這個方向走即可。
  • 如果 \(d_j\) 的下一位為 \(1\),那么有兩種可能:
    • 如果 \(a_i\) 的下一位和 \(c_j\) 的下一位相等,那么無論 \(a_i\) 后面更低的位上填什么,都一定滿足:\(a_i\operatorname{xor} c_j < d_j\)。所以 \(a_i\) 后面的位可以任意填,方案數就是這一整棵子樹里 \(a_i\) 的個數之和。
    • 如果 \(a_i\) 的下一位和 \(c_j\) 的下一位不相等,那么此時 \(a_i\) 仍然是等於 \(c_j \operatorname{xor} d_j\) 的。我們向這個方向走過去,然后考慮更低的位即可。

1.2. b[i] <= d[j] 的 i

對於 \(b_i \leq d_j\)\(i\),答案是 \(\sum_{i} [a_i \operatorname{xor} c_j\leq b_i]\)。看到限制里既有 \(a_i\),又有 \(b_i\),我們難以把它們放到同一個數據結構里去,所以很難實現查詢。換個角度考慮:觀察 \(a_i\operatorname{xor} c_j \leq \min\{b_i, d_j\}\) 這個式子,你會發現 \((a, b)\)\((c, d)\)對稱的。那么,把修改當成詢問做,詢問當成修改做,是不是就和 1.1. 的情況一樣了呢?

具體來說,我們將詢問離線,把所有 \(c_j\),按上述方法(和上面的 \(a_i\) 一樣)插入一個 \(\text{01 trie}\) 中。然后對每組 \((a_i, b_i)\),把它當成上面的 \((c_j, d_j)\),在 \(\text{01 trie}\) 上“查詢”。當然,我們其實不是要查詢一個結果,而是要把它“貢獻”到符合條件的 \(c_j\) 里。在 1.1, 里,我們遇到一整棵符合條件的子樹,就把這個子樹里 \(a_i\) 的數量加入答案中;而現在,我們遇到一整棵符合條件的子樹,就在該節點處打一個標記,表示令子樹里所有 \(c_j\) 的答案加上 \(1\)。最后,每個 \(j\) 的答案,就是 \(c_j\) 到根路徑上的標記之和。


2. 多次詢問時

當然我們不可能每次詢問都重新建兩個 \(\text{trie}\),那樣時間復雜度是 \(\mathcal{O}(qn\log m)\),還不如暴力。

考慮一個簡化的問題:如果只有 \(b_i > d_j\)\(i\)(也就是測試點 \(5\sim 7\)),那么可以在所有詢問開始前,先建好一個可持久化 \(\text{trie}\)。則查詢時,將 \(r_j\)\(l_j - 1\) 兩個時刻的 \(\text{trie}\) 上查詢的結果相減即可。時間復雜度 \(\mathcal{O}(q\log m)\)

考慮另一個簡化的問題:如果只有 \(b_i \leq d_j\)\(i\)(也就是測試點 \(8\sim 11\)),那么可以先將詢問離線,建出所有 \(c_j\)\(\text{trie}\)。然后將詢問拆成兩個:\([1, r_j]\)\([1, l_j - 1]\)(相減得到答案)。現在所有詢問的左端點都是 \(1\)。將詢問按右端點排序,從小到大掃描。每次加入當前的 \(i\)(如前文所述,這個加入操作有點像 1.1. 里的查詢操作,只不過把查詢變成了打標記),然后對右端點為 \(i\) 的詢問統計答案。時間復雜度 \(\mathcal{O}(q\log m)\)

上述兩種情況我們都會做了,那么現在唯一的問題是,怎么把 \(b_i > d_j\)\(i\)\(b_i\leq d_j\)\(i\) 分離出來。考慮把所有 \(b_i, d_j\) 放在一起排序(特別地,\(b_i, d_j\) 相等時,\(b_i\) 放在前)。然后做 \(\text{cdq}\) 分治。那么每次只需要考慮右半邊對左半邊的貢獻。具體來說,取出右半邊的所有 \(a_i\),左半邊的所有 \((c_j,d_j)\),按情況 1.1. 的方法做一次;再取出右半邊的所有 \(c_j\),左半邊的所有 \((a_i,b_i)\),按情況 1.2. 的方法做一次。就求出所有答案了。

每層分治時,做問題 1.1. 和 1.2.,時間復雜度是 \(\mathcal{O}(\mathrm{len}\log m)\)\(\mathrm{len}\) 是當前分治區間的長度),所以總時間復雜度 \(\mathcal{O}((n + q)\cdot \log (n + q)\cdot \log m)\)

參考代碼

因為里面有一個快讀的板子,所以有點長,小伙伴們不要被長度嚇到哦 QAQ

// problem: C
#include <bits/stdc++.h>
using namespace std;

#define mk make_pair
#define fi first
#define se second
#define SZ(x) ((int)(x).size())

typedef unsigned int uint;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int, int> pii;

template<typename T> inline void ckmax(T& x, T y) { x = (y > x ? y : x); }
template<typename T> inline void ckmin(T& x, T y) { x = (y < x ? y : x); }

/* --------------- fast io --------------- */ // begin
namespace Fread {
const int SIZE = 1 << 21;
char buf[SIZE], *S, *T;
inline char getchar() {
	if (S == T) {
		T = (S = buf) + fread(buf, 1, SIZE, stdin);
		if (S == T) return '\n';
	}
	return *S++;
}
} // namespace Fread
namespace Fwrite {
const int SIZE = 1 << 21;
char buf[SIZE], *S = buf, *T = buf + SIZE;
inline void flush() {
	fwrite(buf, 1, S - buf, stdout);
	S = buf;
}
inline void putchar(char c) {
	*S++ = c;
	if (S == T) flush();
}
struct NTR {
	~ NTR() { flush(); }
} ztr;
} // namespace Fwrite
// #ifdef ONLINE_JUDGE
#define getchar Fread :: getchar
#define putchar Fwrite :: putchar
// #endif
namespace Fastio {
struct Reader {
	template<typename T>
	Reader& operator >> (T& x) {
		char c = getchar();
		T f = 1;
		while (c < '0' || c > '9') {
			if (c == '-') f = -1;
			c = getchar();
		}
		x = 0;
		while (c >= '0' && c <= '9') {
			x = x * 10 + (c - '0');
			c = getchar();
		}
		x *= f;
		return *this;
	}
	Reader& operator >> (char& c) {
		c = getchar();
		while (c == ' ' || c == '\n') c = getchar();
		return *this;
	}
	Reader& operator >> (char* str) {
		int len = 0;
		char c = getchar();
		while (c == ' ' || c == '\n') c = getchar();
		while (c != ' ' && c != '\n' && c != '\r') { // \r\n in windows
			str[len++] = c;
			c = getchar();
		}
		str[len] = '\0';
		return *this;
	}
	Reader(){}
} cin;
const char endl = '\n';
struct Writer {
	template<typename T>
	Writer& operator << (T x) {
		if (x == 0) { putchar('0'); return *this; }
		if (x < 0) { putchar('-'); x = -x; }
		static int sta[45];
		int top = 0;
		while (x) { sta[++top] = x % 10; x /= 10; }
		while (top) { putchar(sta[top] + '0'); --top; }
		return *this;
	}
	Writer& operator << (char c) {
		putchar(c);
		return *this;
	}
	Writer& operator << (char* str) {
		int cur = 0;
		while (str[cur]) putchar(str[cur++]);
		return *this;
	}
	Writer& operator << (const char* str) {
		int cur = 0;
		while (str[cur]) putchar(str[cur++]);
		return *this;
	}
	Writer(){}
} cout;
} // namespace Fastio
#define cin Fastio :: cin
#define cout Fastio :: cout
#define endl Fastio :: endl
/* --------------- fast io --------------- */ // end

const int MAXN = 1e5;

int n, a[MAXN + 5], b[MAXN + 5];
int q, ans[MAXN + 5], ql[MAXN + 5], qr[MAXN + 5], c[MAXN + 5], d[MAXN + 5];

pii e[MAXN * 2 + 5];
int vals[MAXN + 5], cnt_val;

pii ee[MAXN * 3 + 5];
int cnt_ee;

int root[MAXN + 5], ch[MAXN * 50 + 5][2], sum[MAXN * 50 + 5], cnt_node;
int new_node(int old) {
	++cnt_node;
	ch[cnt_node][0] = ch[old][0];
	ch[cnt_node][1] = ch[old][1];
	sum[cnt_node] = sum[old];
	return cnt_node;
}
void insert1(int& rt, int y, int v) {
	rt = new_node(y);
	int x = rt;
	sum[x]++;
	for (int i = 23; i >= 0; --i) {
		int t = ((v >> i) & 1);
		ch[x][t] = new_node(ch[y][t]);
		sum[ch[x][t]]++;
		x = ch[x][t];
		y = ch[y][t];
	}
}
int query1(int x, int y, int c, int d) {
	assert(x != 0);
	int res = 0;
	for (int i = 23; i >= 0; --i) {
		if (!x) break;
		int cc = ((c >> i) & 1);
		int dd = ((d >> i) & 1);
		if (dd) {
			res += sum[ch[x][cc]] - sum[ch[y][cc]]; // 嚴格小於 d
		}
		x = ch[x][cc ^ dd];
		y = ch[y][cc ^ dd]; // 等於 d
	}
	res += sum[x] - sum[y];
	return res;
}

void insert2(int x, int v) {
	// 不可持久化
	assert(x != 0);
	for (int i = 23; i >= 0; --i) {
		int t = ((v >> i) & 1);
		if (!ch[x][t]) {
			ch[x][t] = new_node(0);
		}
		x = ch[x][t];
	}
}
void make_contrib2(int x, int c, int d) {
	for (int i = 23; i >= 0; --i) {
		if (!x) break;
		int cc = ((c >> i) & 1);
		int dd = ((d >> i) & 1);
		if (dd) {
			if (ch[x][cc])
				sum[ch[x][cc]]++;
		}
		x = ch[x][cc ^ dd];
	}
	if (x)
		sum[x]++;
}
int query2(int x, int v) {
	int res = 0;
	for (int i = 23; i >= 0; --i) {
		assert(x != 0);
		res += sum[x];
		int t = ((v >> i) & 1);
		x = ch[x][t];
	}
	assert(x != 0);
	res += sum[x];
	return res;
}

void cdq(int l, int r) {
	if (l == r) {
		return;
	}
	int mid = (l + r) >> 1;
	cdq(l, mid);
	cdq(mid + 1, r);
	
	// CASE1: b[i] > d[j], 右邊位置對左邊詢問貢獻
	cnt_val = 0;
	for (int i = mid + 1; i <= r; ++i) {
		if (e[i].se > n)
			continue;
		int idx = e[i].se;
		vals[++cnt_val] = idx;
	}
	sort(vals + 1, vals + cnt_val + 1); // 離散化
	
	cnt_node = 0;
	for (int i = 1; i <= cnt_val; ++i) {
		int idx = vals[i];
		insert1(root[i], root[i - 1], a[idx]);
	}
	for (int i = l; i <= mid; ++i) {
		if (e[i].se <= n)
			continue;
		int idx = e[i].se - n;
		int L = lower_bound(vals + 1, vals + cnt_val + 1, ql[idx]) - vals;
		int R = upper_bound(vals + 1, vals + cnt_val + 1, qr[idx]) - vals - 1;
		if (L > R)
			continue;
		assert(L >= 1 && L <= cnt_val);
		assert(R >= 1 && R <= cnt_val);
		
//		for (int j = L; j <= R; ++j) {
//			ans[idx] += ((a[vals[j]] ^ c[idx]) <= d[idx]);
//		}
		ans[idx] += query1(root[R], root[L - 1], c[idx], d[idx]);
	}
	
	// CASE2: d[j] >= b[i],  右邊的詢問被左邊位置貢獻
	cnt_node = 0;
	new_node(0); // root
	cnt_ee = 0;
	for (int i = mid + 1; i <= r; ++i) {
		if (e[i].se <= n)
			continue;
		int idx = e[i].se - n;
		insert2(1, c[idx]);
		
		ee[++cnt_ee] = mk(ql[idx] - 1, idx + n);
		ee[++cnt_ee] = mk(qr[idx], idx + n + q);
	}
	for (int i = l; i <= mid; ++i) {
		if (e[i].se > n)
			continue;
		int idx = e[i].se;
		ee[++cnt_ee] = mk(idx, idx);
	}
	sort(ee + 1, ee + cnt_ee + 1);
	for (int i = 1; i <= cnt_ee; ++i) {
		if (ee[i].se <= n) {
			int idx = ee[i].se;
			make_contrib2(1, a[idx], b[idx]);
		} else if (ee[i].se <= n + q) {
			int idx = ee[i].se - n;
			ans[idx] -= query2(1, c[idx]);
		} else {
			int idx = ee[i].se - n - q;
			ans[idx] += query2(1, c[idx]);
		}
	}
}

int main() {
//	freopen("island.in", "r", stdin);
//	freopen("island.out", "w", stdout);
	cin >> n >> q;
	for (int i = 1; i <= n; ++i) {
		cin >> a[i] >> b[i];
		e[i] = mk(b[i], i);
	}
	for (int i = 1; i <= q; ++i) {
		cin >> ql[i] >> qr[i] >> c[i] >> d[i];
		e[i + n] = mk(d[i], i + n);
	}
	
	sort(e + 1, e + n + q + 1);
	cdq(1, n + q);
	
	for (int i = 1; i <= q; ++i) {
		cout << ans[i] << endl;
	}
	return 0;
}


免責聲明!

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



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