【题解】P5670 秘籍-反复异或 bitset压位配合线段树

P5670

题意: 维护序列 , 支持以下操作 :

  • 1.区间加上一个数
  • 2.查询区间异或和的后m位(二进制下)

    $ n,q\leq 10^5 ,m\leq 10 $

思维过程:

首先异或和加之间是没有直接性质的(考虑过每一位开一棵线段树,发现不行,因为进位) ,所以难以使用普通的ds维护,因为知道区间异或时整体加是不能 $ O(1) $ 地求出的。

对于普通的 ds 难以维护的题目,考虑分块,却发现分块也卡在了整体打 tag 的一步上,即无法整体 $ O(1) $ 地维护区间异或和随加法的改变量。

由于查询是异或的后 10 位,于是我们大可不必考虑前面的位,即操作是在模 $ 2^m $ 的意义下进行的 。

那么此时模意义下数的种类就只有 $ 2^{10} = 1024 $ 种了。

基于元素考虑不现实,考虑基于统计的算法。

有与异或偶数次等于没有异或,所以本质就是查询出现的奇偶性。

加法导致无法按位考虑,所以直接的想法是暴力开 1024 个桶,统计每个数出现的奇偶性。

然后加就是将桶“旋转”,如下:

假设在模意义下 $ +x $ 。

原本在 $ 0 $ 的值会到达 $ x $ , $ k $ 会到达 $ (k + x) \mod n $ 。

(我甚至去想了 splay )

然后发现由于奇偶性是布尔型的,于是直接使用 bitset 维护!

具体做法:

具体做法就是开一棵线段树,每一个点存一个 bitset 。

区间加时若整个区间被包含,则直接打标记,更改这个点的 bitset 的值,使用位运算优化后 “旋转操作如下” :

s = ( (s << d) | (s >> 1024 - d) ) ;

bitset 左移、右移 多出的位会自动补 0 , 很好理解。

区间查询相同,在线段树上把大区间分成很多个小区间,将代表着这些区间的 bitset 异或起来即可得到整个区间出现次数的奇偶性,遍历该 bitset 即可。

时间复杂度 :

$ O((n+q) \log n \times \frac a w + q a) $ 其中 $ a=1024 $

前半部分是线段树的复杂度计算方式本身复杂度乘上 push_up/down 的复杂度即可。

后半部分是遍历 bitset 的复杂度。

有关细节:

本题时限较小,略微卡常,线段树写结构体能让内存访问更连续,大幅减小常数。

由于所有 $ m $ 都不大于 10 , 直接维护后 10 位的值,输出答案处理后 $ m $ 位即可。

tag加完了要记得取模 1024 或者 与 1023 , 因为在模意义下,即取后 10 位。

屁话:

这题评不了紫吧 (>_<)

代码:

代码较长,放在此处比较占版面,放出关键代码 (线段树) 。

部分代码

struct node{
    int tag ; 
    state s ;
    inline void get(int d) 
    {
        tag += d ;
        tag&=1023 ;
        s = ( (s << d) | (s >> 1024 - d) ) ;
    }
} T[N<<2];
#define mid ((l+r)>>1)
#define ls x<<1,l,mid
#define rs x<<1|1,mid+1,r
inline void push_up(int x)
{
    T[x].s = T[x<<1].s ^ T[x<<1|1].s;
}

inline void build(int x,int l,int r)
{
    if(l==r) {
        T[x].tag = 0 ; 
        (T[x].s).set(a[l],1);
        return ;
    }
    build(ls);
    build(rs);
    push_up(x);
}

inline void push_down(int x)
{
    if(!T[x].tag) return ;
    T[x<<1].get(T[x].tag);
    T[x<<1|1].get(T[x].tag); 
    T[x].tag = 0 ; 
}

inline void update(int x,int l,int r,int L,int R,int c)
{
    if(L<=l && r<=R) {T[x].get(c); return ;}
    push_down(x);
    if(L<=mid) update(ls,L,R,c);
    if(R>mid) update(rs,L,R,c);
    push_up(x) ;
}

inline state query(int x,int l,int r,int L,int R)
{
    if(L<=l && r<=R) return T[x].s ;
    push_down(x);
    if(L>mid) return query(rs,L,R);
    if(R<=mid) return query(ls,L,R);
    return query(ls,L,R) ^ query(rs,L,R);
}

全代码

AC记录

跑的比指令集慢多了

辛苦管理员大大审核到这里,感谢你看到这里!

posted @ 2022-03-31 19:52  寂静的海底  阅读(7)  评论(0编辑  收藏  举报  来源