异或线性基学习笔记

    void ins(ll x){
        for(ll i=64; i>=0; i--)
            if(x>>i){
                if(!p[i]) {p[i]=x; return;}
                else x^=p[i];
            }
    }

根据以上代码构造的异或线性基有性质:

  • \(p_i>p_j\) 当且仅当 \(i>j\)\(p_i\ne 0\)\(p_j\ne 0\),反之亦成立。
  • \(p_i\ne 0\)\(p_i \& 2^{i} = 2^{i}\),第 \(i+1\)\(\infty\) 均为 \(0\)(二进制下)。

因此 \(x\) 在插入时会不断变小直到可以插入线性基或变成 \(0\)

线性基合并

P3292 [SCOI2016]幸运数字:A 国共有 n 座城市,这些城市由 n-1 条道路相连,使得任意两座城市可以互达,且路径唯一。每座城市都有一个幸运数字,以纪念碑的形式矗立在这座城市的正中心,作为城市的象征。一些旅行者希望游览 A 国。旅行者计划乘飞机降落在 x 号城市,沿着 x 号城市到 y 号城市之间那条唯一的路径游览,最终从 y 城市起飞离开 A 国。在经过每一座城市时,游览者就会有机会与这座城市的幸运数字拍照,从而将这份幸运保存到自己身上。然而,幸运是不能简单叠加的,这一点游览者也十分清楚。他们迷信着幸运数字是以异或的方式保留在自己身上的。例如,游览者拍了 3 张照片,幸运值分别是 5,7,11,那么最终保留在自己身上的幸运值就是 9(5 xor 7 xor 11)。有些聪明的游览者发现,只要选择性地进行拍照,便能获得更大的幸运值。例如在上述三个幸运值中,只选择 5 和 11 ,可以保留的幸运值为 14 。现在,一些游览者找到了聪明的你,希望你帮他们计算出在他们的行程安排中可以保留的最大幸运值是多少。

考虑倍增线性基,询问时直接倍增合并即可。部分代码:

inline void merge(ll *x, ll *y){
    for(int i=0; i<=60; i++)
        if(y[i]) ins(x, y[i]);
}
inline ll ask(){
    ll ans=0;
    for(ll i=60; i>=0; i--)
        if((ans^res[i])>ans) ans^=res[i];
    return ans;
}
void dfs(int u, int fa){
    dep[u]=dep[fa]+1;
    ST[u][0]=fa; ins(st[u][0], val[u]);
    for(int i=1; i<=20; i++)
        ST[u][i]=ST[ST[u][i-1]][i-1],
        memcpy(st[u][i], st[u][i-1], sizeof st[u][i]),
        merge(st[u][i], st[ST[u][i-1]][i-1]);
    for(int i=h[u]; i; i=nt[i]){
        int v=to[i];
        if(v==fa) continue;
        dfs(v, u);
    }
}
ll Get(ll x, ll y){
    memset(res, 0, sizeof res);
    if(dep[x]<dep[y]) swap(x, y);
    for(int i=20; i>=0; i--) if(dep[ST[x][i]]>=dep[y])
                merge(res, st[x][i]), x=ST[x][i];
    if(x==y){ ins(res, val[y]); return ask(); }
    for(int i=20; i>=0; i--) if(ST[x][i]!=ST[y][i]){
            merge(res, st[x][i]);
            merge(res, st[y][i]);
            x=ST[x][i], y=ST[y][i];
        }
    ins(res, val[x]); ins(res, val[y]); ins(res, val[ST[x][0]]);
    return ask();
}

普通最大路径异或和(1->n)

P4151 [WC2011]最大XOR和路径:考虑一个边权为非负整数的无向连通图,节点编号为 \(1\)\(N\),试求出一条从 \(1\) 号节点到 \(N\) 号节点的路径,使得路径上经过的边的权值的 XOR 和最大。路径可以重复经过某些点或边,当一条边在路径中出现了多次时,其权值在计算 XOR 和时也要被计算相应多的次数,具体见样例。对于 \(100 \%\) 的数据,\(N \leq 50000\)\(M \leq 100000\)\(D_i \leq 10^{18}\)

以任意一条 \(1\to n\) 的路径为基(值为 \(val\)),不断地增加一些环即可得到最优解,只需将所有环的值插入线性基中,答案为与 \(val\) 异或的最大值。

证明:

从任意一点 \(x\) 到达一个环,将环遍历一遍然后原路返回即可将该环的异或值加入答案中。

修改路径同理,只是环中包含的原路径的一些边。

void dfs(int u, ll Now){
    dist[u]=Now;
    vis[u]=1;
    for(int i=h[u]; i; i=nt[i])
        if(!vis[to[i]]) dfs(to[i], Now^w[i]);
        else ji.ins(Now^dist[to[i]]^w[i]);
}
int n, m;
int main(){
    scanf("%d %d", &n, &m);
    for(int i=1, x, y; i<=m; i++){
        static ll z;
        scanf("%d %d %lld", &x, &y, &z);
        link(x, y, z);
    }
    dfs(1, 0);
    cout<<ji.getmax(dist[n])<<'\n';
    return 0;
}

线性基内元素个数固定,与元素的插入顺序无关

P4570 [BJWC2011]元素:一般地,矿石越多则法力越强,但物极必反:有时,人们为了获取更强的法力而使用了很多矿石,却在炼制过程中发现魔法矿石全部消失了,从而无法炼制出法杖,这个现象被称为“魔法抵消” 。特别地,如果在炼制过程中使用超过一块同一种矿石,那么一定会发生“魔法抵消”。后来,随着人们认知水平的提高,这个现象得到了很好的解释。经过了大量的实验后,著名法师 Dmitri 发现:如果给现在发现的每一种矿石进行合理的编号(编号为正整数,称为该矿石的元素序号),那么,一个矿石组合会产生“魔法抵消”当且仅当存在一个非空子集,那些矿石的元素序号按位异或起来为零(如果你不清楚什么是异或,请参见下一页的名词解释 )。例如,使用两个同样的矿石必将发生“魔法抵消”,因为这两种矿石的元素序号相同,异或起来为零。并且人们有了测定魔力的有效途径,已经知道了:合成出来的法杖的魔力等于每一种矿石的法力之和。人们已经测定了现今发现的所有矿石的法力值,并且通过实验推算出每一种矿石的元素序号。 现在,给定你以上的矿石信息,请你来计算一下当时可以炼制出的法杖最多有多大的魔力。对于全部的数据:\(1\leq N \leq 1000\)\(1\leq \mathrm{Number}_i \le 10^{18}\)\(1\leq \mathrm{Magic}_i \le 10^4\)

当插入线性基内的元素固定时,线性基内元素个数固定,与元素的插入顺序无关。考虑贪心的插入法力最大的矿石,并记录答案。

区间 xor 解决方式(差分)

P5607 [Ynoi2013] 无力回天:给你一个长度为 n 的整数序列 \(a_1\), \(a_2\), \(\ldots\), \(a_n\) ,你需要实现以下两种操作,每个操作都可以用四个整数 \(opt\;l\;r\;v\) 来表示:

\(opt=1\) 时,代表把一个区间 \([l,r]\) 内的所有数都 xor 上 \(v\)

\(opt=2\) 时, 查询一个区间 \([l,r]\) 内选任意个数(包括 \(0\) 个)数 xor 起来,这个值与 \(v\) 的最大 xor 和是多少。

第一行有两个正整数 \(n,m\)。第二行有 \(n\) 个整数表示给你的序列。 之后 \(m\) 行每行有四个整数 \(opt, l, r, v\)表示一个操作。

对于每个 \(opt=2\) 的操作,输出一行一个数表示答案~

input:                                     output:
4 5                                        61
1 14 51 4                                  63
2 1 3 0                                    560
1 2 3 3
2 1 4 10
1 1 4 514
2 3 4 2

对于 \(100\%\) 的数据,满足 \(1 \le n , m \le 5 \times 10^4\),值域在 \([0,10^9]\) 之间。

最大异或和,线性基无疑了,但是这个区间 \(\operatorname{xor}\) 似乎无法维护的样子,考虑挖掘一下区间 \(\operatorname{xor}\) 和线性基之间的性质。

不明显的,设 \(b_i=a_i\operatorname{xor} a_{i-1}\),则 \(a_l,b_{l+1},\dots,b_{r}\) 的线性基与 \(a_l,a_{l+1},\dots,a_{r}\) 的线性基是等价的,因为 \(b_i=a_i\operatorname{xor} a_{i-1}\)\(a_i=a_l\operatorname{xor}b_{l+1}\operatorname{xor}\dots\operatorname{xor}b_i\)

因此我们只需要维护四种操作:区间 \(\operatorname{xor}\),单点查询值,单点 \(\operatorname{xor}\),询问区间线性基。线段树维护后两种操作,树状数组维护前两种操作。

对于最大异或和,直接贪心求解即可。

时间复杂度为 \(O((n+m)\log n\log^2V)\)

简单单点加入、区间询问线性基

P4839 P 哥的桶:

P 哥现在有 \(n\) 个桶,它们排成了一排,这些桶可以装下任意多个球。每个球有一个固定的价值。P 哥时不时地会找新球,并把新找的球丢进某个桶里面。我们用 \(1\;k\;x\) 来表示 P 哥找了一个价值为 \(x\) 的球,并且丢进了 \(k\) 号桶里面。P 哥每次会在特定的桶里面拿出一些球。我们用 \(2\;l\;r\) 来表示 P 哥在 \(l\) 号桶到 \(r\) 号桶之间拿球。P 哥希望拿出来的球的价值异或和尽可能大。

第一行两个整数 \(n, m\),依次表示 P 哥的操作次数、这组数据会涉及到的最大编号。接下来 \(n\) 行,每行三个整数,表示操作。操作格式如题。

对于每个 2 操作,输出 P 哥拿出的球的最大价值异或和。

input:                          output:
5 3                             3
1 1 2                           7
1 2 3
1 3 4
2 1 2
2 1 3

对于 \(100 \%\) 的数据,满足 \(1 \le n, m \leq 5 \times 10^4\)\(1 \le l\leq r\leq m\)\(1 \le k \leq m\)\(0 \le x \leq 2^{31}-1\)

异或和最大,线性基无疑了。考虑用线段树维护线性基,对于插入操作直接插入即可,因为只有 \(\log n\) 个节点会改变,所以一次插入的时间复杂度是 \(\log n\log V\)\(V\) 是值域)的,对于询问操作,直接将与询问区间有关的节点代表的线性基合并到一个大的线性基中,答案即为大的线性基的最大异或和,因为有 \(\log n\) 个节点,合并的复杂度是 \(\log^2V\) 的,所以询问的时间复杂度为 \(\log n\log^2V\)

posted @ 2023-06-07 18:40  fzrcy  阅读(63)  评论(0)    收藏  举报