题解 [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;
}