UOJ681 【UR #22】月球列车 题解
题目描述
给定 \(n\) 个整数 \(a_i\) , \(m\) 次询问,每次给定 \(x\) ,求 \(\bigoplus_{i=1}^n(a_i+x)\) ,强制在线。
数据范围
- \(1\le n,m\le2.5\cdot10^5\) 。
- \(0\le a_i,x\lt 2^{60}\) 。
时间限制 \(\texttt{3s}\) ,空间限制 \(\texttt{512MB}\) 。
分析
参考了这篇博客。
注意到异或有一个很重要的性质:不同位相互独立。这启示我们对每一位分开考虑。
记 \(g_j(x)\) 为 \(x\) 二进制表示中第 \(j\) 位的值。则:
前两项对 \(i\) 求异或和是容易的,最后一项本质是 \(j-1\to j\) 的进位。
对 \(a_i\&(2^j-1)\) 排序并二分,可以做到 \(\mathcal O((n+m)\log n\log V)\) ,可惜还是过不去。
对 \(\forall 0\le j\lt 60\) ,记按照 \(0\sim j\) 位排序后的结果为 \(b_{j,1},\cdots,b_{j,n}\) 。
想办法优化掉 \(\log n\) ,我们需要做这样两件事情:
- 在 \(\mathcal O(n\log V)\) 的时间内求出 \(b_{j,i}\) 。
- 在 \(\mathcal O(1)\) 的时间内,找到 \(x\&(2^j-1)\) 的在 \(b_{j-1,*}\) 数组中
lower_bound的位置。
前者本质上就是一个基数排序。
对于后者,我们需要预处理一点东西。
记 \(s_{0/1,j,i}\) 为 \(b_{j-1,1},\cdots,b_{j-1,i}\) 中第 \(j\) 位为 \(0/1\) 的数的个数。
如果 \(j=0\) 怎么办?没有关系,用乱序的 \(a\) 直接算就行了,因为此时我们只关心 \(s_{0/1,0,n}\) 的值。
询问时升序枚举 \(j\) ,动态维护 cur 表示能让 \(j\to j+1\) 进位的 \(a_i\) 个数。
-
如果
(v>>j&1)==1,那么进位有两种情况。第一种是
(a[i]>>(j-1)&1)==0,并且a[i]+v通过进位导致第j-1位为 \(1\) 。第二种是
(a[i]>>(j-1)&1)==1,但是相加时低位不进位。不要天真地以为答案就是
s[1][j][n]+cur,因为会算重!容斥计算不满足条件的 \(a_i\) 个数,可以得到
cur=n-s[0][j][n-cur]。统计答案时,目标求出
\sum (a[i]+v)>>j&1。a[i]第 \(j\) 位为 \(0\) 时要求不进位,也就是一段前缀。a[i]第 \(j\) 位为 \(1\) 时要求进位,也就是一段后缀。注意根据
cur的定义,先统计答案再更新cur。 -
如果
(v>>j&1)==0,那么进位的条件要苛刻一些。首先
a[i]的第 \(j\) 位一定是 \(1\) ,其次相加时低位也要进位。统计个数就是一段后缀和,执行
cur=s[1][j][n]-s[1][j][n-cur]即可。统计答案刚好和第一种情况相反。
a[i]第 \(j\) 位为 \(0\) 时要求进位,也就是一段后缀。a[i]第 \(j\) 位为 \(1\) 时要求不进位,也就是一段前缀。
时间复杂度 \(\mathcal O(n\log V)\)。
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int maxn=2.5e5+5;
int m,n,t,cnt;
ll v,res;
ll a[maxn],b[65][maxn];
int s[2][65][maxn];
int main()
{
scanf("%d%d%d",&n,&m,&t);
for(int i=1;i<=n;i++) scanf("%lld",&a[i]);
for(int j=0;j<=60;j++)
{
for(int i=1;i<=n;i++)
{
s[0][j][i]=s[0][j][i-1],s[1][j][i]=s[1][j][i-1];
s[a[i]>>j&1][j][i]++;
}
int cnt=0;
for(int i=1;i<=n;i++) if(!(a[i]>>j&1)) b[j][++cnt]=a[i];
for(int i=1;i<=n;i++) if(a[i]>>j&1) b[j][++cnt]=a[i];
for(int i=1;i<=n;i++) a[i]=b[j][i];
}
while(m--)
{
scanf("%lld",&v),v^=t*(res>>20),res=0;
for(int j=0,cur=0;j<=60;j++)
{
if(v>>j&1)
{
res^=((s[0][j][n-cur]+s[1][j][n]-s[1][j][n-cur])&1ll)<<j;
cur=n-s[0][j][n-cur];
}
else
{
res^=((s[1][j][n-cur]+s[0][j][n]-s[0][j][n-cur])&1ll)<<j;
cur=s[1][j][n]-s[1][j][n-cur];
}
}
printf("%lld\n",res);
}
return 0;
}
本文来自博客园,作者:peiwenjun,转载请注明原文链接:https://www.cnblogs.com/peiwenjun/p/16464499.html
浙公网安备 33010602011771号