[UR#22] 月球列车
一、题目
这种比较精细的题还是要多练练,其实不怎么难但是我看题解都看了三个小时
二、解法
位运算和四则运算混合在一起是很恶心的,方法基本上只有按位考虑。
对于数位 \(w\),我们只需要考虑 \(a_i/x\) 在数位 \(w\) 出现次数的奇偶性,和考虑 \(a_i+x\) 在 \(w-1\) 这个数位上进位次数的奇偶性即可,前者特别容易计算,难点是后者。
一定要记住这种进位问题是有单调性的,我们把 \(a_i\) 按前 \(w-1\) 位从小到大排序后,能产生进位的一定是一段后缀,设 \(V=2^w-x\and(2^w-1)\),进位的充要条件是 \(a_i\and(2^w-1)\geq V\)
如果暴力预处理排序、查询时候暴力二分的话时间复杂度 \(O(n\log n\log a)\)
考虑优化,因为二进制的特性我们可以在排序的时候使用归并排序。查询时候我们想快速得到 \(V\) 在数组上的位置,可以预处理 \(c[w][i][0/1]\) 表示以前第 \(i\) 个位置上的数,考虑第 \(w\) 位之后,不加 \(2^w/\)加上 \(2^w\) 在新数组中的位置。
假设我们知道 \(V\) 以前在数组中的位置,可以通过跳 \(c\) 数组来处理出 \(V\) 在数组中的新位置,这样求出来的是小于等于 \(V\) 的最后一个位置。对于计算答案,如果 \(x\) 这一位为 \(0\) 那么我们找在 \([V,V+2^w)\) 中的数个数,因为 \(a_i\geq V+2^w\) 会因为进位和原本这一位就有值贡献被抵消了;如果 \(x\) 这一位为 \(1\) 那么贡献的数全部取反即可。
时间复杂度 \(O(n\log a)\),真的是道签到题呢
#include <cstdio>
#include <cstring>
const int M = 250005;
#define int long long
int read()
{
int x=0,f=1;char c;
while((c=getchar())<'0' || c>'9') {if(c=='-') f=-1;}
while(c>='0' && c<='9') {x=(x<<3)+(x<<1)+(c^48);c=getchar();}
return x*f;
}
int n,m,t,a[M],c[64][M][2],tmp[M],cur[M];
int work(int x)
{
int cur=n,ans=0;
for(int w=0;w<=60;w++)
{
int cnt=c[w][cur][1]-c[w][cur][0];
if(!((x>>w)&1)) cur=c[w][cur][1];
else cnt=n-cnt,cur=c[w][cur][0];
if(cnt&1) ans|=(1ll<<w);
}
return ans;
}
signed main()
{
n=read();m=read();t=read();
for(int i=1;i<=n;i++)
a[i]=read(),cur[i]=i;
for(int w=0;w<=60;w++)
{
int cnt=0;
c[w][0][0]=cnt;
for(int i=1;i<=n;i++)
{
if(!((a[cur[i]]>>w)&1)) tmp[++cnt]=cur[i];
c[w][i][0]=cnt;
}
c[w][0][1]=cnt;
for(int i=1;i<=n;i++)
{
if((a[cur[i]]>>w)&1) tmp[++cnt]=cur[i];
c[w][i][1]=cnt;
}
memcpy(cur,tmp,sizeof cur);
}
int ans=0;
for(int i=1;i<=m;i++)
{
int x=read();
if(t) x^=(ans>>20);
ans=work(x);
printf("%lld\n",ans);
}
}