2020 CCPC Wannafly Winter Camp Day6 H. 异或询问 (异或性质&&前缀和)

题意

给定一个序列 a 1 , 2.. n a_{1,2..n} a1,2..n,定义 f ( x ) = ( a 种 小 于 等 于 x 的 数 字 数 目 ) 2 f(x)=(a种小于等于x的数字数目)^2 f(x)=(ax)2
Q Q Q个询问   l   r   x \ l\ r\ x  l r x,查询 ∑ i = l r f ( i   x o r   x ) \sum_{i=l}^rf(i\ xor\ x) i=lrf(i xor x)
数据范围:
1 ≤ n , Q ≤ 1 e 5 , 0 ≤ l ≤ r < 2 30 , 0 ≤ x , a i < 2 30 1\le n,Q\le1e5,0\le l \le r < 2^{30},0\le x,a_i<2^{30} 1n,Q1e5,0lr<230,0x,ai<230

解题思路

区间的询问可以转换为对两个前缀 s u m ( n ) = ∑ i = 0 n f ( i   x o r   x ) sum(n)=\sum_{i=0}^nf(i\ xor\ x) sum(n)=i=0nf(i xor x)的询问。答案是两个前缀相减: s u m ( r ) = s u m ( l − 1 ) sum(r)=sum(l-1) sum(r)=sum(l1)
查询的i和一个乱七八糟的x异或了一下,导致询问的实际区间不是连续的,很麻烦。但是由异或的性质我们可以注意到,这些不连续的询问是由最多 l o g ( x ) log(x) log(x)个连续的询问构成的:
比如查询的是 s u m ( n ) sum(n) sum(n)
从高位到低位,比如进行到了 p o s pos pos位,如果n的这一位是1,那么i的这一位为0的时候,后面 ( i   x o r   x ) (i\ xor\ x) (i xor x)可以取的区间范围是 [ 0 , 2 p o s − 1 ] [0,2^{pos}-1] [0,2pos1]中的任意值。那么就得到了一个连续的区间。对这log个连续的区间进行查询就可以了。
对连续区间的答案查询有很多方法,这里我采用的是先利用前缀和处理出每一个 a i a_i ai对应的 s u m ( a i ) sum(a_i) sum(ai),然后查询的时候二分一下就可以了。

#include
#define ll long long
#define lowbit(x) ((x)&(-(x)))
#define mid ((l+r)>>1)
#define lson rt<<1, l, mid
#define rson rt<<1|1, mid+1, r
using namespace std;
const int maxn = 1e5 + 50;
const ll mod = 998244353;
int a[maxn];
int cc[maxn], num = 0;
int n, q;
ll sum[maxn];
void init(){
    cin>>n>>q;
    for(int i = 0; i < n; ++i) scanf("%d", &a[i]), cc[++num] = a[i];
    sort(a, a+n);
    sort(cc+1,cc+num+1);
    num = unique(cc+1,cc+num+1)-cc-1;
    sum[0] = 0;
    for(int i = 1; i < num; ++i){
        ll k = upper_bound(a,a+n,cc[i])-a;
        sum[i] = (sum[i-1]+(cc[i+1] - cc[i])%mod*k%mod*k%mod)%mod;
    }
}
ll ask(int x){
    int pos = upper_bound(cc+1,cc+1+num, x)-cc;
    if(pos == 1) return 0;
    pos--;
    ll res = sum[pos-1];
    ll k = upper_bound(a, a+n, cc[pos])-a;
    res = (res + (x-cc[pos]+1)*k%mod*k%mod)%mod;
    return res;
}
ll ask(int l, int r){
    //cout<<"l:"<
    return (ask(r) - ask(l-1))%mod;
}
ll qry(int t, int x){
    if(t < 0) return 0;
    int cur = 0;
    ll res = 0;
    for(int i = 30; i >= 0; --i){
        int u = (x>>i&1);
        if(t>>i&1){
            res += ask(cur|(u<<i), (cur|(u<<i))+(1<<i)-1);
            cur += ((1<<i)^(u<<i));
        }else{
            cur |= (u<<i);
        }
    }
    res += ask(cur, cur);
    return res;
}
void sol(){
    while(q--){
        int l, r, x;
        scanf("%d%d%d", &l, &r, &x);
        ll ans = (qry(r, x) - qry(l-1, x))%mod;
        ans = (ans + mod)%mod;
        printf("%lld\n", ans);
    }
}
int main()
{
    init();
    sol();
}

你可能感兴趣的:(二分,训练补题)