[CQOI2021模拟]芬威克树

137 篇文章 1 订阅
98 篇文章 0 订阅

题目

题目描述
众所周知,芬威克树是一个优秀的数据结构,可以 O ( log ⁡ 2 n ) \mathcal O(\log_2n) O(log2n) 的插入与查询。

可是,查询远远多于插入呢?那么我们就有了一个改进算法, k k k 进制操作。代码如下:

void add(int x,int v){
	while(x <= n){
        s[x] = s[x]^v;
        x = x+lowbitv(x);
    }
}
int query(int x,int v){
    int ans = 0;
    while(x > 0){
        ans = ans^s[x];
        x = x-lowbit(x);
    }
    return ans;
}

其中, l o w b i t v \rm lowbitv lowbitv 返回 x x x k k k 进制下的位值。例如 k = 5 k=5 k=5 时,由于 120 = 4 × 5 2 + 4 × 5 1 120=4\times 5^2+4\times 5^1 120=4×52+4×51,此时 l o w b i t v ( 120 ) {\rm lowbitv}(120) lowbitv(120) 将会返回 5 1 = 5 5^1=5 51=5

与之相对的, l o w b i t \rm lowbit lowbit 会返回这一位的值。例如 k = 5 k=5 k=5 l o w b i t ( 120 ) = 4 × 5 1 = 20 {\rm lowbit}(120)=4\times 5^1=20 lowbit(120)=4×51=20

显然它能够成功维护前缀异或和,并且插入是 O ( k log ⁡ k n ) \mathcal O(k\log_k n) O(klogkn) 的,但是查询是 O ( log ⁡ k n ) \mathcal O(\log_k n) O(logkn) 的。

然而蠢蛋 O n e I n D a r k \sf OneInDark OneInDarkadd 中的 lowbitv 写成了 lowbit!现在,告诉你本题的输入,请预测 O n e I n D a r k \sf OneInDark OneInDark 的输出。

数据范围与提示
n ≤ 1 0 9 ,    q ≤ 2 × 1 0 5 ,    k ≤ 2 × 1 0 5 n\le 10^9,\;q\le 2\times 10^5,\;k\le 2\times 10^5 n109,q2×105,k2×105

对于 30 % 30\% 30% 的数据,满足 2 ∤ k 2\nmid k 2k

思路

树状数组本来就是棵树。即使是 k k k 进制也是如此。考虑建出这棵树,即 x x x x + l o w b i t ( x ) x+{\rm lowbit}(x) x+lowbit(x) 连边。如果 x + l o w b i t ( x ) x+{\rm lowbit}(x) x+lowbit(x) 超过 n n n 了,就连向超级根。

事实上,它的重要前提是,query 是没有写错的!复杂度是 O ( log ⁡ k n ) \mathcal O(\log_kn) O(logkn) 的!

有一个奇特的部分分,是 k k k 为奇数。为什么要保证这个玩意儿?

直观地看,最低位原本是 x ( x ≠ 0 ) x(x\ne 0) x(x=0) 的话,父节点就变为 2 x   m o d   k 2x\bmod k 2xmodk,以此类推, i i i 级祖先就是 2 i x   m o d   k 2^ix\bmod k 2ixmodk,显然它仍然非零。

如果从树上来看,树是一条链。因为子节点必然是 2 − 1 x   m o d   k 2^{-1}x\bmod k 21xmodk,是否进位也是由二者的大小关系决定的。那么用线段树维护即可。

推广到 k = 2 x ⋅ t    ( k=2^{x}\cdot t\;( k=2xt( t t t 是奇数 ) ) ) 的情况。是否仍然有 “链式树” 的美妙性质呢?考虑最低位是 p = 2 y ⋅ q    ( p=2^{y}\cdot q\;( p=2yq( q q q 是奇数 ) ) ) 的情况。与上面的类似,考虑 2 2 2 是否有助于成为 k k k 的倍数。如果 y ≥ x y\ge x yx 但是 p ≠ 0 p\ne 0 p=0,那么只可能是 q < t q<t q<t 了。此时,无论乘多少个 2 2 2 都没用,它只会让 y y y 徒劳地增加,然后
2 y ⋅ q − k = 2 x ⋅ ( 2 y − x q − t ) 2^{y}\cdot q-k=2^x\cdot (2^{y-x}q-t) 2yqk=2x(2yxqt)

无非是 { q ′ = 2 y − x q − t y ′ = x \begin{cases}q'=2^{y-x}q-t \\ y' = x\end{cases} {q=2yxqty=x 罢了,仍然不是 t t t 的倍数。总结一下:如果最低位的质因子 2 2 2 次数不低于 k k k 的质因子 2 2 2 的次数,那么 l o w b i t v \rm lowbitv lowbitv 不会改变

仔细看,由于 y ≥ x y\ge x yx,至少要乘一个 2 2 2,所以对 k k k 取模时, y − x ≥ 1 y-x\ge 1 yx1,进而说明 2 y − x q − t 2^{y-x}q-t 2yxqt 是奇数。你能发现这意味着什么吗?——新的数字 p ′ = 2 x q ′ p'=2^{x}q' p=2xq 满足 q ′ q' q 为奇数,而未减去 k k k 时至少是 2 y + 1 q ′    ( x ≤ y ) 2^{y+1}q'\;(x\le y) 2y+1q(xy)

如果最低位 p = 2 y ⋅ q p=2^y\cdot q p=2yq 满足 y = x y=x y=x,那么只可能是取模了 k k k 之后的结果(我们只考虑 y ≥ x y\ge x yx 的点)。即 q = 2 y ′ − x q ′ − t    ( q=2^{y'-x}q'-t\;( q=2yxqt( q ′ q' q 为奇数 ) ) ),其中 t , q , x t,q,x t,q,x 均已知。显然是唯一解。

如果 y > x y>x y>x,子节点当然就是 2 y − 1 p 2^{y-1}p 2y1p,因为百分之百没有取模 k k k 。综上可知, y ≥ x y\ge x yx 时,其祖先的子节点“唯一”。换句话说, y ≥ x y\ge x yx 时,树退化成了“链”。(事实上,它应当理解为树链剖分,固定 y ≥ x y\ge x yx 的点是重儿子。上面所说的,证明了这是合法的链剖分。)这谁能发现啊?怪不得国集爷 C h a r l o t t e ′ s    S p a c e \sout{\rm Charlotte's\; Space} CharlottesSpace也没有切

那么 y < x y<x y<x 就不太幸运了。干脆 O ( x ) \mathcal O(x) O(x) 暴力做吧!这样一来,要么 y = x y=x y=x,进入一条链;要么过程中变为 0 0 0,进位了。一共只有 O ( log ⁡ k n ) \mathcal O(\log_k n) O(logkn) 位,每一位是 O ( x = log ⁡ 2 k ) \mathcal O(x=\log_2 k) O(x=log2k) 次跳跃,相乘得到 O ( log ⁡ 2 n ) \mathcal O(\log_2 n) O(log2n),可以接受。总结起来就是,用极小的代价让它进入链

看来,一切问题的根源都在链上了。感觉可以线段树?只需要一个编号。只需要知道它是这条链上的第几个数。可以从上面数,但是不容易。试着从下面数——它到叶子的距离。

不难发现,最低位的变化是周期性的,因为下一个值 x ′ = 2 x   m o d   k x'=2x\bmod k x=2xmodk,仅由上一个数决定。一个周期内的进位次数也是一样的。叶子节点,我们可以肯定,在 k k k 进制下仅有一个数位(否则,可以计算出重儿子的最低位,大不了前面退一位,重儿子一定存在)。那么,从叶子节点到当前节点,进位次数为除去最低位及更小数位后得到的数字。即,若 l o w b i t v ( x ) = k y {\rm lowbitv}(x)=k^y lowbitv(x)=ky 的话,进位次数为 ⌊ x k y + 1 ⌋ \lfloor\frac{x}{k^{y+1}}\rfloor ky+1x

我们能否求出周期呢?答案是显然的。因为 [ 1 , k − 1 ] [1,k-1] [1,k1] 每个数都必然在一种循环内,总长度是 O ( k ) \mathcal O(k) O(k) 的。同时我们也能求出其进位次数。现在我们知道,一个循环会进位 t 0 t_0 t0 次,而总进位次数是 t = ⌊ x k y + 1 ⌋ t=\lfloor{x\over k^{y+1}}\rfloor t=ky+1x,肯定是 ⌊ t t 0 ⌋ \lfloor{t\over t_0}\rfloor t0t 个完整的循环,剩下一个 t   m o d   t 0 t\bmod t_0 tmodt0 的零碎次数。完全可以预处理出,每种循环的第 i i i 次进位在哪里。那就可以 O ( 1 ) \mathcal O(1) O(1) 查询了。

不幸的是,我们要求出 t t t,需要 O ( log ⁡ k n ) \mathcal O(\log_k n) O(logkn) 的复杂度。如果你 丧心病狂 追求极致,可以 O ( n ) \mathcal O(\sqrt n) O(n ) 预处理 log ⁡ k n 2 \frac{\log_k n}{2} 2logkn 位的数字,可以 O ( 2 ) \mathcal O(2) O(2) 查询。这都是小打小闹,不去纠结。

找到了链底,又知道了距离,还是没法编号。其实,你已经知道了,链底的最低位只可能是 2 x ⋅ q ( 2^x\cdot q( 2xq( q q q 是奇数 ) ) ) 嘛。一共 log ⁡ k n \log_k n logkn 位,也就是 O ( k log ⁡ k n ) \mathcal O(k\log_k n) O(klogkn) 个不同的链底。那么你对它们都建立动态开点线段树, O ( log ⁡ 2 n ) \mathcal O(\log_2 n) O(log2n) 插入也百死而无憾!

时间复杂度呢?插入时,首先产生 O ( log ⁡ 2 n ) \mathcal O(\log_2 n) O(log2n) 个零散单点,然后 O ( log ⁡ 2 n ) \mathcal O(\log_2 n) O(log2n) 插入线段树。查询时, O ( log ⁡ k n ) \mathcal O(\log_k n) O(logkn) 个单点,要么 O [ log ⁡ 2 ( q log ⁡ 2 n ) ] \mathcal O[\log_2(q\log_2 n)] O[log2(qlog2n)] 在零散单点的 s e t \rm set set 里查询,要么 O ( log ⁡ n ) \mathcal O(\log n) O(logn) 在线段树里查询。那么复杂度就是
O [ k + q log ⁡ k n ⋅ log ⁡ 2 n ] \mathcal O[k+q\log_k n\cdot \log_2 n] O[k+qlogknlog2n]

实在是太奇妙啦!

代码

常数比较大,不写输出优化就会 T L E \tt TLE TLE 了……估计数据也不是很强,之前有个错误的写法都能过……

#include <cstdio>
#include <iostream>
#include <cstring>
#include <vector>
#include <map>
using namespace std;
inline int readint(){
	int a = 0; char c = getchar(), f = 1;
	for(; c<'0'||c>'9'; c=getchar())
		if(c == '-') f = -f;
	for(; '0'<=c&&c<='9'; c=getchar())
		a = (a<<3)+(a<<1)+(c^48);
	return a*f;
}
inline void writeint(int x){
	if(x > 9) writeint(x/10);
	putchar((x-x/10*10)^48);
}

const int MaxK = 200005;

int n, k;

vector< int > item[MaxK];
// x = item[bel[x]][pos[x]]
int bel[MaxK], pos[MaxK];
// index of i-th upload in a group
vector< int > up[MaxK];
// prefix upload times
vector< int > s[MaxK];
int cnt2[MaxK]; // = lowbit(x,2)
void prepare(){
	for(int i=1; i<=k; ++i)
		if(i&1) cnt2[i] = 0; // 2^0
		else cnt2[i] = cnt2[i>>1]+1;
	memset(bel,-1,k<<2);
	for(int i=0; i<k; ++i){
		if(cnt2[i] < cnt2[k]) continue;
		if(~bel[i]) continue; // processed
		item[i].resize(1);
		item[i].push_back(i);
		for(int j=(i<<1)%k; j&&j!=i; j=(j<<1)%k)
			item[i].push_back(j); // collect
		const int &cnt = item[i].size();
		s[i].resize(cnt); s[i][0] = 0;
		up[i].resize(cnt);
		for(int j=1,jb=0; j<cnt; ++j){
			pos[item[i][j]] = j;
			bel[item[i][j]] = i;
			s[i][j] = s[i][j-1];
			if((item[i][j]<<1) >= k)
				++ s[i][j];
			if(cnt2[item[i][j]] == cnt2[k])
				up[i][++ jb] = j;
		}
	}
}

/** @brief lowbitv(x) = k^{p} */
int lowbit(int x){
	if(!x) while(true);
	static int lb[MaxK], done = false;
	static int block, block_len;
	if(!done){
		for(int i=1; i<MaxK; ++i)
			if(i%k) lb[i] = 0;
			else lb[i] = lb[i/k]+1;
		done = true, block = 1;
		block_len = 0;
		while(block*k <= MaxK)
			block *= k, ++ block_len;
	}
	for(int i=0; true; i+=block_len)
		if(x%block)
			return lb[x%block]+i;
		else x /= block;
}
/** @brief lowbit(x) = p */
int lowbitv(int x){
	static int lb[MaxK], done = false;
	static int block;
	if(!done){
		for(int i=1; i<MaxK; ++i)
			if(i%k) lb[i] = 1;
			else lb[i] = lb[i/k]*k;
		done = true, block = 1;
		while(block*k <= MaxK)
			block *= k;
	}
	for(int i=1; true; i*=block)
		if(x%block)
			return lb[x%block]*i;
		else x /= block;
}

int query(int x,int &top){
//printf("start query %d\n",x);
	int p = lowbit(x); // for hash id
	int t = lowbitv(x);
	int d = (x/t)%k; // the digit
	t = x/t/k; // how many periods
	int len = s[bel[d]].size()-1;
//printf("len = %d\n",len);
	int t0 = s[bel[d]][len];
	int res = (t/t0)*len; // complete period
	t %= t0; // last upload steps
	/* find x in up such that
	** s[pos-1]-s[x-1] == t */ ;
	int now = s[bel[d]][pos[d]-1];
	if(now >= t){ // in one period
		top = up[bel[d]][now-t+1];
		res += pos[d]-top;
		top = item[bel[d]][top];
	}
	else{
		t -= now; // t-th from back
		top = up[bel[d]][t0-t+1];
		res += pos[d]+len-top;
		top = item[bel[d]][top];
	}
//printf("query %d %d, ",x,k);
//printf("top = %d, dis = %d\n",top+p*k,res);
	top += p*k; return res+1;
}

namespace SgTree{
	const int MaxN = 5000000;
	int v[MaxN], son[MaxN][2], cntNode;
	void insert(int &o,int l,int r,int x,int y){
//printf("inserting %d %d %d %d %d\n",o,l,r,x,y);
		if(!o) v[o = ++ cntNode] = 0;
		if(l == r) return void(v[o] ^= y);
		if(x <= ((l+r)>>1))
			insert(son[o][0],l,(l+r)>>1,x,y);
		else insert(son[o][1],(l+r)/2+1,r,x,y);
		v[o] = v[son[o][0]]^v[son[o][1]];
	}
	int query(int o,int l,int r,int ql,int qr){
//printf("querying %d %d %d %d %d\n",o,l,r,ql,qr);
		if(!o || qr < l || r < ql) return 0;
		if(ql <= l && r <= qr) return v[o];
		return query(son[o][0],l,(l+r)>>1,ql,qr)
			^ query(son[o][1],(l+r)/2+1,r,ql,qr);
	}
}
int rt[MaxK*30];

map< int,int > sing; // single
void modify(int x,int v){
	while(x <= n){
		int t = lowbitv(x);
		int d = (x/t)%k; // the digit
//printf("%d += %d*%d ?\n",x,t,d);
		if(cnt2[d] < cnt2[k])
			sing[x] ^= v, x += t*d;
		else break; // in the chain
	}
	if(x > n) return ;
//puts("BAD");
	int top, dis = query(x,top);
	SgTree::insert(rt[top],1,n,dis,v);
}
int query(int x){
	int res = 0;
	for(int t,d; x; x-=t*d){
		t = lowbitv(x), d = (x/t)%k;
//printf("start x = %d\n",x);
		if(cnt2[d] < cnt2[k]){
			if(!sing.count(x))
				continue;
			res ^= sing[x];
		}
		else{
			int top, dis = query(x,top);
			res ^= SgTree::query(
				rt[top],1,n,1,dis);
		}
//printf("end x = %d\n",x);
	}
	return res;
}

int main(){
//	freopen("C:\\Users\\hp\\Desktop\\3.16\\data\\fenwick\\fenwick7.in","r",stdin);
//	freopen("fenwick.out","w",stdout);
	n = readint();
	int q = readint();
	k = readint(); prepare(); 
	for(int i=1,opt,x; i<=q; ++i){
		opt = readint(), x = readint();
		if(opt == 1) modify(x,readint());
		else writeint(query(x)), putchar('\n');
	}
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值