「LibreOJ NOI Round #1」北校门外的回忆(找性质+倍增+线段树)
对于\(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));
}
}
}
转载注意标注出处:
转自Cold_Chair的博客+原博客地址