peiwenjun's blog 没有知识的荒原

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\) 位的值。则:

\[g_j(a_i+x)=g_j(a_i)\oplus g_j(x)\oplus[(a_i\&(2^j-1))+(x\&(2^j-1))\ge 2^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;
}

posted on 2022-07-11 00:13  peiwenjun  阅读(50)  评论(0)    收藏  举报

导航