「LibreOJ NOI Round #1」北校门外的回忆(找性质+倍增+线段树)

https://loj.ac/problem/510

对于\(n<=100000\)的,\(x\)\(x+low(x)\)连边,构成内向森林,那么每次就是修改一条到根的链,和询问一个点。

对于\(n\)很大的,我们要思考更深层的东西。

考虑为什么\(k>2\)时add循环很多次。

比如\(k=3\),一开始最低位\(=1\),每次\(*2~mod~k\),会在1、2、1、2循环,就达到了\(O(n)\)

考虑k是奇数,会一直在一位循环,\(x\)\(x+low(x)\)形成的森林其实是若干条链,因为不可能有其它的边插进来有(有2的逆元)。

\(k\)不是奇数时,不仅可能加着加着这一位变成了0,跳到更高的位,也可能一个点有多条入边。

事实上,如果只考虑含有2的指数\(>=k含有的\),就是若干条链了,这相当于k的2的部分无用了。

而在一位上,最多乘个log次含有2的指数就\(>=k含有的了\),要不就中途就跳到下一层了。

所以,从任何一点,走\(log\)次就会走到链上。

链上可以用动态开点线段树维护,问题是怎么找到一个链的开头?

倍增:预处理\(f[i][j]\)表示,某一位是i,要使高位\(-2^j\),会跳到哪里,每次跳即可。

时间复杂度:\(log^2\)

Code:

#include<bits/stdc++.h>
#define fo(i, x, y) for(int i = x, _b = y; i <= _b; i ++)
#define ff(i, x, y) for(int i = x, _b = y; i <  _b; i ++)
#define fd(i, x, y) for(int i = x, _b = y; i >= _b; i --)
#define ll long long
#define pp printf
#define hh pp("\n")
using namespace std;

int calc(int x) {
	int s = 0;
	while(x % 2 == 0) x /= 2, s ++;
	return s;
}

const int M = 19260817;
const int N = (2e5 + 5) * 31;

struct hash {
	int fi[M];
	int h[N]; int nt[N], c[N], tot;
	int& operator[] (int n) {
		int y = n % M;
		for(int p = fi[y]; p; p = nt[p])
			if(h[p] == n) return c[p];
		nt[++ tot] = fi[y], h[tot] = n, fi[y] = tot;
		return c[tot];
	}
	int find(int n) {
		int y = n % M;
		for(int p = fi[y]; p; p = nt[p])
			if(h[p] == n) return c[p];
		return 0;
	}
} f, rt;

#define i0 t[i].l
#define i1 t[i].r
struct tree {
	int l, r, x;
} t[N]; int tt;
int pl, pr, px;
void add(int &i, int x, int y) {
	if(y < pl || x > pr) return;
	if(!i) i = ++ tt;
	t[i].x ^= px;
	if(x == y) return;
	int m = x + y >> 1;
	add(i0, x, m); add(i1, m + 1, y);
}
void ft(int i, int x, int y) {
	if(!i || y < pl || x > pr) return;
	if(x >= pl && y <= pr) { px ^= t[i].x; return;}
	int m = x + y >> 1;
	ft(i0, x, m); ft(i1, m + 1, y);
}

int n, q, k, k2;

int op, x, y;

int w[32][200005], w0[200005];

void build() {
	ff(i, 1, k) {
		int t = calc(i);
		if(t >= k2) {
			w0[i] = t > k2 ? w0[i / 2] : i;
			w[0][i * 2 % k] = w0[i];
		}
	}
	fo(j, 1, 31) ff(i, 1, k)
		w[j][i] = w[j - 1][w[j - 1][i]];
}

int low(int x) {
	int s = 1;
	while(x % k == 0) s *= k, x /= k;
	return x % k * s;
}

int low2(int x) {
	int s = 1;
	while(x % k == 0) s *= k, x /= k;
	return x % k;
}

int qv(int x) {
	int s = 1;
	while(x % k == 0) s *= k, x /= k;
	return s;
}

void add(int x, int y) {
	while(x <= n) {
		if(calc(low2(x)) >= k2) break;
		f[x] ^= y;
		x += low(x);
	}
	if(x > n) return;
//	pp("%d %d\n", x, y);
	int l = low(x);
	int v = qv(x);
	int s = (x - l) / v / k;
	int z = w0[l / v];
	fd(i, 31, 0) if(s >> i & 1) z = w[i][z];
	pl = pr = x; px = y;
	add(rt[z * v], 1, n);
}

int sum(int x) {
	int ans = 0;
	for(; x; ) {
		ans ^= f.find(x);
		
		int l = low(x);
		if(calc(low2(x)) >= k2) {
			int v = qv(x);
			int s = (x - l) / v / k;
			int z = w0[l / v];
			fd(i, 31, 0) if(s >> i & 1) z = w[i][z];
			pl = 1, pr = x, px = 0;
			ft(rt.find(z * v), 1, n);
			ans ^= px;
		}
		
		x -= l;
	}
	return ans;
}

int main() {
	scanf("%d %d %d", &n, &q, &k);
	k2 = calc(k);
	build();
	fo(i, 1, q) {
		scanf("%d %d", &op, &x);
		if(op == 1) {
			scanf("%d", &y);
			add(x, y);
		} else {
			pp("%d\n", sum(x));
		}
	}
}
posted @ 2020-04-09 19:32  Cold_Chair  阅读(269)  评论(0编辑  收藏  举报