LOJ 510: 「LibreOJ NOI Round #1」北校門外的回憶

題目傳送門:LOJ #510數組

題意簡述:

給出一個在 $K$ 進制下的樹狀數組,可是它的實現有問題。函數

形式化地說,令 $\mathrm{lowbit}(x)$ 爲在 $K$ 進制下的 $x$ 的最低非零位的值,例如 $\mathrm{lowbit} !\left( 230_{(5)} \right)! = 30_{(5)}$。spa

add(x, v) 操做執行的是:
當 $x \le n$ 時,執行 s[x] ^= v,並將 $x$ 變爲 $x + \mathrm{lowbit}(x)$,並繼續循環。code

query(x) 操做執行的是:
令 $ans$ 的初始值爲 $0$。
當 $x > 0$ 時,執行 ans ^= s[x],並將 $x$ 變爲 $x - \mathrm{lowbit}(x)$,並繼續循環。
最終返回 $ans$。get

題解:

不一樣於正確寫法,add 操做執行時的運算次數不是 $\mathcal O ( K \log_K n )$ 的,而是 $\mathcal O (n)$ 的,觀察什麼時候它的複雜度會發生變化:
當 $K = 3, x = 1$ 時,$x$ 的變化爲(十進制下):$1 \to 2 \to 4 \to 5 \to 7 \to 8 \to 10 \to \cdots$。it

能夠發現,$x$ 在 $K$ 進制下的最低非零位的位置始終不變,並且最低非零位的值陷入了 $1, 2, 1, 2, \ldots$ 的循環。io

對於不一樣的 $K$,咱們觀察特定的最低非零位的值的變化,發現有的最終變成了 $0$,即這一位再也不是非零的,而有的落入了循環中。class

很顯然,這其實就是在函數 $f(x) = 2x \bmod K$ 對應的圖上轉移的過程,這張圖是一個基環內向森林,其中有一個連通份量的根是 $0 \to 0$ 的自環,與 $0$ 相連的點最終都會提高最低非零位。循環

數論告訴咱們,每棵基環內向樹的深度都不會超過 $\log_2 K$,若是所在的是根爲 $0$ 的基環內向樹,最低位也只會變化 $\log_K n$ 次,總次數爲 $\log_2 K \times \log_K n = \log_2 n$,因此這部分暴力跳便可。方法

當最終落到了不爲 $0$ 的環上時,參考上面的例子,會造成一條無限向後延伸的鏈,可是它是一個相鄰位置的差有循環節的鏈。
若是將每條鏈預處理出來,就能夠對每條鏈開一個樹狀數組,以維護後綴加,單點查的操做。
可是這裏值域太大,沒法直接預處理,因此考慮用倍增的方法直接求出該點在鏈上的編號,即 $x$ 是在當前鏈上的第幾個位置,就能夠直接進行樹狀數組操做了(固然此時值域仍然是極大的,可是能夠經過哈希的方法將改動的位置映射到小數組中)。

詢問的時候同理,query 時 $x$ 只會變化 $\log_K n$ 次,因此能夠每次直接查,首先判斷 $x$ 是否在鏈上,若是不是直接修改 $ans$ 便可,不然須要在當前鏈對應的樹狀數組上進行單點查的操做(前面的修改對應的是後綴加),作個差分就能夠變成單點加、前綴查了,就是經典的樹狀數組操做,對於大值域同理採用哈希的方法便可。

下面是代碼,假設哈希的時間複雜度爲 $\mathcal O (1)$,則總時間複雜度爲 $\mathcal O (K \log n + q \log_K n \log n)$:

#include <cstdio>

const int HASH = 19260817, NUMS = 10000005;
int h[HASH], nxt[NUMS], to[NUMS], tot;
inline int Hasher(int val, int typ) {
	int cyp = (val ^ val >> 1) % HASH;
	for (int i = h[cyp]; i; i = nxt[i]) if (to[i] == val) return i;
	return typ ? nxt[++tot] = h[cyp], to[tot] = val, h[cyp] = tot : 0;
}

typedef long long LL;
const int MK = 200005;

int N, K, Q, V[6], C;
inline void lowbit(int x, int &y, int &z) {
	int s = 1, t = C;
	while (t) { if (x % V[t] == 0) x /= V[t], s *= V[t]; --t; }
	y = x % K, z = y * s;
}
int ltrs[MK][30], ntrs[MK][30];
LL lsum[MK][30], nsum[MK][30];
int arr[NUMS];

int main() {
	scanf("%d%d%d", &N, &Q, &K);
	for (LL x = K; x <= N; x *= x) V[++C] = x;
	for (int i = 1; i < K; ++i) if ((i & -i) >= (K & -K))
		ltrs[i * 2 % K][0] = lsum[i * 2 % K][0] = i,
		ntrs[i][0] = i * 2 % K, nsum[i][0] = i;
	for (int j = 0; j < 29; ++j)
		for (int i = 1; i < K; ++i) if ((i & -i) >= (K & -K))
			ltrs[i][j + 1] = ltrs[ltrs[i][j]][j],
			lsum[i][j + 1] = lsum[i][j] + lsum[ltrs[i][j]][j],
			ntrs[i][j + 1] = ntrs[ntrs[i][j]][j],
			nsum[i][j + 1] = nsum[i][j] + nsum[ntrs[i][j]][j];
	for (int i = 1; i <= Q; ++i) {
		int op, x, y, z, v, d;
		scanf("%d%d", &op, &x);
		if (op == 1) {
			scanf("%d", &v);
			while (x <= N) {
				lowbit(x, y, z);
				if ((y & -y) >= (K & -K)) {
					int id = 0, X = x, Y = y, w = z / y;
					for (int j = 29; j >= 0; --j)
						if (lsum[Y][j] < X / w)
							id |= 1 << j,
							X -= lsum[Y][j] * w,
							Y = ltrs[Y][j];
					++id;
					for (int j = 0; x <= N; ++j) if (id >> j & 1) {
						arr[Hasher(x, 1)] ^= v;
						x += nsum[y][j] * w;
						y = ntrs[y][j];
						id += 1 << j;
					}
					break;
				}
				arr[Hasher(x, 1)] ^= v;
				x += z;
			}
		} else {
			int Ans = 0;
			while (x) {
				lowbit(x, y, z);
				if ((y & -y) >= (K & -K)) {
					int id = 0, X = x, Y = y, w = z / y;
					for (int j = 29; j >= 0; --j)
						if (lsum[Y][j] < X / w)
							id |= 1 << j,
							X -= lsum[Y][j] * w,
							Y = ltrs[Y][j];
					++id;
					X = x, Y = y;
					for (int j = 0; id; ++j, id >>= 1) if (id & 1) {
						if ((d = Hasher(X, 0))) Ans ^= arr[d];
						X -= lsum[Y][j] * w;
						Y = ltrs[Y][j];
					}
				} else if ((d = Hasher(x, 0))) Ans ^= arr[d];
				x -= z;
			}
			printf("%d\n", Ans);
		}
	}
	return 0;
}
相關文章
相關標籤/搜索