题解 [UR #22] 月球列车

传送门

真的非常神仙的签到题(?)
题解看了一上午才看懂

首先考虑按位确定
考虑一个答案的这一位就是 \(\oplus_{i=1}^n[(a_i\oplus x)的这一位为1]\oplus [(a_i+x)向这一位产生进位]\)
前面一项的异或和是容易计算的,关键是后面一项
\(mask=2^{j+1}-1\),发现 \((a_i+x)\) 向第 j 位产生进位当且仅当 \(mask\&a_i+mask\&x\geqslant 2^j\)
移项得 \(2^j-mask\&x\leqslant mask\&a_i\)
那么可以对每个 \(j\) 提前将所有 \(mask\&a_i\) 进行排序,每次询问时二分
复杂度 \(O(n\log n\log V)\)

然而还有复杂度更优的做法:
考虑预处理的排序是每次加一个最高位,这个东西是容易归并做到 \(O(n\log n)\)
然后去优化那个二分
我是要在一些顺次归并得到的数组上做二分,那么这个二分是否也能用归并代替呢?
发现上个数组中的每个数加上 \(0\)\(2^j\) 就得到了当前数组
那么考虑这样一个做法:令 \(c_{j, i, 0/1}\) 表示考虑到第 \(j\) 位,当前排名为 \(i\) 的元素加上 \(0/2^{j+1}\) 后在第 \(j+1\) 位的数组中的排名
那么在最低位找到要查的值的排名,然后每次跳这个数组
这个东西的正确性在于对于只考虑低于 \(j\) 位形成的有序数组 \(a\),若有 \(x\in[a_k, a_{k+1})\)
则在加上新的第 \(j\) 位后形成的数组 \(b\) 中不会有数 \(\in[c_{j-1, k, x\&2^{j-1}}, x加上第 j 位]\)
反证若存在这样一个数一定 \(\in[a_k, x]\) 即可
这样就做到了 \(O(n\log n)\)
细节有一堆,注意在数组中插一个 0 和一个 inf 或使用等效实现

  • 貌似不带修查询区间 \(\oplus_{i=l}^r (a_i+x)\) 可以权值主席树做到单次 \(O(log^2 n)\)
点击查看代码
#include <bits/stdc++.h>
using namespace std;
#define INF 0x3f3f3f3f
#define N 1000010
#define fir first
#define sec second
#define ll long long
//#define int long long

char buf[1<<21], *p1=buf, *p2=buf;
#define getchar() (p1==p2&&(p2=(p1=buf)+fread(buf, 1, 1<<21, stdin)), p1==p2?EOF:*p1++)
inline ll read() {
	ll ans=0, f=1; char c=getchar();
	while (!isdigit(c)) {if (c=='-') f=-f; c=getchar();}
	while (isdigit(c)) {ans=(ans<<3)+(ans<<1)+(c^48); c=getchar();}
	return ans*f;
}

int n, m, t;
ll a[N], sta[N], sum;
int rk[65][N][2], top;

signed main()
{
	n=read(); m=read(); t=read();
	for (int i=1; i<=n; ++i) sum^=(a[i]=read());
	for (int i=0; i<63; ++i) {
		top=0;
		for (int j=0; j<=n+1; ++j) {
			if (j&&j<=n&&(a[j]&(1ll<<i))) sta[++top]=a[j];
			rk[i][j][1]=top;
		}
		for (int j=0; j<=n+1; ++j) {
			if (j&&j<=n&&(!(a[j]&(1ll<<i)))) sta[++top]=a[j];
			rk[i][j][0]=top;
		}
		for (int j=1; j<=top; ++j) a[j]=sta[j];
	}
	ll ans, lst=0, x;
	for (int i=1; i<=m; ++i) {
		x=read()^(t*(lst>>20));
		int now=n; ans=(sum^(n&1?x:0))&1;
		for (int j=0; j<62; ++j) {
			ll mask=(1ll<<j+1)-(((1ll<<j+1)-1)&x);
			// cout<<bitset<30>(mask)<<endl;
			now=rk[j][now][mask&(1ll<<j)?1:0];
			// now=0; for (int k=1; k<=n; ++k) if ((((1ll<<j+1)-1)&a[k])>=mask) ++now;
			// cout<<"now: "<<now<<endl;
			if (mask==(1ll<<j+1)) {if ((sum^(n&1?x:0))&(1ll<<j+1)) ans|=1ll<<j+1;}
			else {if ((sum^(n&1?x:0)^(now&1?1ll<<j+1:0))&(1ll<<j+1)) ans|=1ll<<j+1;}
		}
		printf("%lld\n", lst=ans);
	}

	return 0;
}
posted @ 2022-05-25 15:49  Administrator-09  阅读(5)  评论(0编辑  收藏  举报