UOJ681 【UR #22】月球列车

给一个长度为 \(n\) 的序列,每次询问给定一个数,问你所有数加上这个数后的异或和,询问相互独立。

首先如果是加上一个数,结合上次做 \(\text{Ynoi}\) 的时候的想法,感觉是要倒建 Trie 树的,然后直接处理就行了吧。但是好像我每次都是遍历整颗树的,考虑如何只遍历一边的树,还是通过枚举位数来计算?

好像存在了一个离线的做法,就是每次考虑添加一个二进制位,具体方法和加一是类似的,离线下来添加即可。


我们考虑 \(a_i+v\) 的第 \(j\) 位是 \(a_i\oplus v\) 的第 \(j\) 位异或上 \(a_i+v\) 的第 \(j-1\) 位是否进位。对于一个数是否进位实际上很好判断,考虑该数在前 \(j-1\) 位上的值与 \(v\) 在前 \(j-1\) 位上的值相加是否大于等于 \(2^j\) ,即

\[a_i\and(2^j-1)+v\and(2^j-1)\ge 2^j\\ a_i\and(2^j-1)\ge2^j-v\and(2^j-1)\\ \]

考虑这个进位实际上是单调的,所以我们如果将 \(a_i\) 按照前 \(j-1\) 位排序,就可以比较轻易地找到进位的范围。

如果预处理排序且二分都是暴力进行的话,总复杂度应该是 \(O(n\log n\log a)\) 的。常数优秀的小朋友这个时候可能已经可以过了,但是可以继续优化。

考虑如果是给前若干位的二进制排序的话,实际上可以利用归并排序的类似思想优化,同时我们的二分也可以在这个上面优化。

考虑我们已经知道了 \(j-1\) 层的末尾 \(x\) 个数是进位的,同时维护一个数组表示在按照前 \(j-1\) 排序的情况下,前缀的 \(01\) 个数。那么如果 \(v\) 的第 \(j\) 位是 \(1\) ,考虑可以进位的数就是该位为 \(1\) 的数和为 \(0\) 却进位的数;如果 \(v\) 的第 \(j\) 位是 \(0\) ,考虑可以进位的数是上一层进位的数中该位为 \(1\) 的数。


感觉不是一道很难的题,但是没做出来,做题可以考虑更加大胆一点,将性质考虑的全面一点,即使看上去可能没啥前途的也可以稍微想想。

#include<bits/stdc++.h>
using namespace std;
const int N=2.5e5+5,K=61;
int n,m,t;long long lstans=0;
long long a[N],b[N];int c[2][K][N];
int main(){
	cin>>n>>m>>t;
	for(int i=1;i<=n;++i) scanf("%lld",&a[i]);
	for(int j=0;j<K;++j){
		for(int i=1;i<=n;++i){
			c[0][j][i]=c[0][j][i-1],c[1][j][i]=c[1][j][i-1];
			c[(a[i]>>j)&1][j][i]++;
		}
		for(int i=1;i<=n;++i) b[i]=a[i];
		for(int i=1,L=1;i<=n;++i) if(!((b[i]>>j)&1)) a[L++]=b[i];
		for(int i=n,R=n;i>=1;--i) if((b[i]>>j)&1) a[R--]=b[i];
	}
	for(int i=1;i<=m;++i){
		long long v;scanf("%lld",&v);
		v=(v^(t*(lstans/(1ll<<20)))),lstans=0;
		for(int j=0,x=0;j<K;++j){
			if((v>>j)&1){
				int cnt=c[0][j][n-x]+c[1][j][n]-c[1][j][n-x];
				x=n-c[0][j][n-x],lstans+=(1ll<<j)*(cnt&1);
			}
			else{
				int cnt=c[1][j][n-x]+c[0][j][n]-c[0][j][n-x];
				x=c[1][j][n]-c[1][j][n-x],lstans+=(1ll<<j)*(cnt&1);
			}
		}
		printf("%lld\n",lstans);
	}
	return 0;
}
posted @ 2022-01-18 18:28  Point_King  阅读(87)  评论(0编辑  收藏  举报