[学习笔记] 线性基

你说我一个连线性基都不会的人怎么可能走的远,我跟你说我也是这么想的,但是你先别急。

一、线性基

OI 中常用全部的就是 \(2\) 进制下的异或线性基。

线性基就是可以把一个集合里的数转化成一组基,使得这组基里所有 xor 出来的结果于原集合 xor 出来的结果完全一致。

这是一个线性基模板:

struct linear_basis_awa {
  // If don't use "query_kth",
  // please "//" cy[64], bt[64], build_kth(), query_kth(k) and upd thanks meow!
  ll bs[64], cy[64], bt[64];
  int cnt, upd, num;
  inline void init () {
    upd = 1;
    for (int i = 0;i < 64; ++ i) bs[i] = 0;
    cnt = 0;
  }
  inline void ins (ll x) {
    if (!x) return ;
    upd = 1;
    for (int i = 62;i >= 0; -- i) {
      if (x & (1ll << i)) {
        if (bs[i]) x ^= bs[i];
        else {
          cnt ++;
          bs[i] = x;
          break;
        }
      }
    }
  }
  inline ll query_maxxor (ll x) {
    ll res = x;
    for (int i = 62;i >= 0; -- i) {
      if ((res ^ bs[i]) > res) res ^= bs[i];
    }
    return res;
  }
  inline void merge_bas (linear_basis_awa O) {
    for (int i = 0;i < 64; ++ i) ins (O.bs[i]);
  }
  inline void operator = (linear_basis_awa O) {
    for (int i = 0;i < 64; ++ i) bs[i] = O.bs[i];
    for (int i = 0;i < 64; ++ i) cy[i] = O.cy[i];
    upd = O.upd;
    cnt = O.cnt;
  }
  inline void build_kth () {
    upd = 0;
    for (int i = 0;i < 64; ++ i) cy[i] = bs[i], bt[i] = 0;
    for (int i = 62;i >= 0; -- i) {
      for (int j = i - 1;j >= 0; -- j) {
        if (cy[i] & (1ll << j)) cy[i] ^= cy[j];
      }
    }
    num = 0;
    for (int i = 0;i <= 62; ++ i) {
      if (cy[i]) bt[num ++] = cy[i];
    }
  }
  inline ll query_kth (ll k) {
    if (k == 1) return 0;
    k --;
    if (upd) build_kth ();
    if ((1ll << num) <= k) return -1ll;
    ll ans = 0;
    for (int i = 0;i <= 62; ++ i) {
      if (k & (1ll << i)) {
        ans ^= bt[i];
      }
    }
    return ans;
  }
};

其中这里的 \(bs_{w}\) 是最高位为 \(2^w\) 的基向量。

线性基的插入

如果 \(x\) 这个数内包含 \(2^w\) 这一位,如果基中已经有,那么直接异或掉 \(2^w\),否则那就直接加入 \(x\),然后结束,时间复杂度 \(O(\log w)\)

线性基求最大 xor 和

对于 query_maxxor (int v) 函数的解释:查询 \(\displaystyle \max_{S \subseteq \text{base}} \left[\left(\bigoplus_{x \in S} x\right) \oplus v\right]\);做法:贪心,如果 \(2^x\) 这一位异或下来是 \(1\),那么一定要比后面所有位都是 \(1\) 产生的贡献之和(\(2^x - 1\))还要大,所以我们从高位往低位贪心肯定是最优的,时间复杂度 \(O(\log w)\)

线性基 xor 值计数

因为线性基里的数都是线性无关的,所以任意一个子集(不含 \(0\))xor 出来的值都互不相同,如果含 \(0\) 总共就是 \(2^{cnt}\) 种情况,其中 \(cnt\) 是基中非 \(0\) 的数的数量。

线性基合并

直接将一个线性基里的数暴力插入到另一个线性基里即可,时间复杂度 \(O(\log^2 w)\)

线性基求第 \(k\) 小 xor 值

首先说一下,这个第 \(k\) 小是 去重后,含 \(\textbf{0}\)\(k\) 小。

模板题:[ABC283G] Partial Xor Enumeration / https://www.luogu.com.cn/problem/AT_abc283_g

首先我们把基的每一个数都与别的数分离开来。

然后将不为 \(0\) 的数组成一个序列,直接查询异或和的第 \(k\) 小即可。

具体可以看代码。

#include <bits/stdc++.h>
#include <ext/pb_ds/assoc_container.hpp>
#include <ext/pb_ds/tree_policy.hpp>
#include <ext/pb_ds/hash_policy.hpp>
#include <ext/rope>
using namespace std;
using namespace __gnu_pbds;
using namespace __gnu_cxx;
#ifdef LOCAL
#include "algo/debug.h"
#else
#define debug(...) 42
#endif
typedef long long ll;
typedef pair < int, int > PII;
typedef int itn;
mt19937 RND_MAKER (chrono :: steady_clock :: now ().time_since_epoch ().count ());
inline ll randomly (const ll l, const ll r) {return (RND_MAKER () ^ (1ull << 63)) % (r - l + 1) + l;}
//bool Mbes;
const double pi = acos (-1);
//__gnu_pbds :: tree < Key, Mapped, Cmp_Fn = std :: less < Key >, Tag = rb_tree_tag, Node_Upadte = null_tree_node_update, Allocator = std :: allocator < char > > ;
//__gnu_pbds :: tree < PPS, __gnu_pbds :: null_type, less < PPS >, __gnu_pbds :: rb_tree_tag, __gnu_pbds :: tree_order_statistics_node_update > tr;
inline bool statisqwq (char c) {
  if (c < '0') return true;
  if (c > '9') return true;
  return false;
}
inline int read () {
  int x = 0, f = 0;
  char c = getchar ();
  for ( ; statisqwq (c) ; c = getchar ()) f = (c == '-') ? 1 : f;
  for ( ; c >= '0' && c <= '9' ; c = getchar ()) x = (x << 1) + (x << 3) + (c & 15);
  return !f ? x : -x;
}
inline ll readll () {
  ll x = 0, f = 0;
  char c = getchar ();
  for ( ; statisqwq (c) ; c = getchar ()) f = (c == '-') ? 1 : f;
  for ( ; c >= '0' && c <= '9' ; c = getchar ()) x = (x << 1) + (x << 3) + (c & 15);
  return !f ? x : -x;
}
const ll hashmod1 = 2147483579;
const ll hashmod2 = 1610612741;
const ll hashmod3 = 805306457;
inline void chkmax (int &x, int y) {x = max (x, y);}
inline void chkmin (int &x, int y) {x = min (x, y);}
inline void chkmax2 (ll &x, ll y) {x = max (x, y);}
inline void chkmin2 (ll &x, ll y) {x = min (x, y);}
int n;
ll l, r;
struct linear_basis_awa {
  // If don't use "query_kth",
  // please "//" cy[64], bt[64], build_kth(), query_kth(k) and upd thanks meow!
  ll bs[64], cy[64], bt[64];
  int cnt, upd, num;
  inline void init () {
    upd = 1;
    for (int i = 0;i < 64; ++ i) bs[i] = 0;
    cnt = 0;
  }
  inline void ins (ll x) {
    if (!x) return ;
    upd = 1;
    for (int i = 62;i >= 0; -- i) {
      if (x & (1ll << i)) {
        if (bs[i]) x ^= bs[i];
        else {
          cnt ++;
          bs[i] = x;
          break;
        }
      }
    }
  }
  inline ll query_maxxor (ll x) {
    ll res = x;
    for (int i = 62;i >= 0; -- i) {
      if ((res ^ bs[i]) > res) res ^= bs[i];
    }
    return res;
  }
  inline void merge_bas (linear_basis_awa O) {
    for (int i = 0;i < 64; ++ i) ins (O.bs[i]);
  }
  inline void operator = (linear_basis_awa O) {
    for (int i = 0;i < 64; ++ i) bs[i] = O.bs[i];
    for (int i = 0;i < 64; ++ i) cy[i] = O.cy[i];
    upd = O.upd;
    cnt = O.cnt;
  }
  inline void build_kth () {
    upd = 0;
    for (int i = 0;i < 64; ++ i) cy[i] = bs[i], bt[i] = 0;
    for (int i = 62;i >= 0; -- i) {
      for (int j = i - 1;j >= 0; -- j) {
        if (cy[i] & (1ll << j)) cy[i] ^= cy[j];
      }
    }
    num = 0;
    for (int i = 0;i <= 62; ++ i) {
      if (cy[i]) bt[num ++] = cy[i];
    }
  }
  inline ll query_kth (ll k) {
    if (k == 1) return 0;
    k --;
    if (upd) build_kth ();
    if ((1ll << num) <= k) return -1ll;
    ll ans = 0;
    for (int i = 0;i <= 62; ++ i) {
      if (k & (1ll << i)) {
        ans ^= bt[i];
      }
    }
    return ans;
  }
};
linear_basis_awa awa;
//bool Meds;
signed main () {
  n = read (), l = readll (), r = readll ();
  awa.init ();
  for (int i = 1;i <= n; ++ i) {
    ll w = readll ();
    awa.ins (w);
  }
  for (ll k = l;k <= r; ++ k) {
    printf ("%lld ", awa.query_kth (k));
  }
  printf ("\n");
  return 0;
}

好,这下线性基的操作你都会了,开始挑战黑题吧!

线性基,启动!

image

https://www.luogu.com.cn/training/360943

  • P3812 【模板】线性基:板子题。

  • P3857 [TJOI2008] 彩灯:也是很板的题。

  • P3292 [SCOI2016] 幸运数字:把线性基套在树上倍增上即可。

  • P4839 P 哥的桶:线段树 + 线性基(单点修改,区间查询)

  • P4570 [BJWC2011] 元素:按贡献从大到小插入看有没有变化即可,正确性类似于 Kruskal。

  • P5607 [Ynoi2013] 无力回天 NOI2017:将序列按 xor 差分,\(a_{l \sim r}\) 的线性基转化为 \(a_l, d_{l + 1 \sim r}\) 的线性基了,其中 \(d_i = a_i \oplus a_{i - 1}\),然后 \(d\) 可以使用线性基 + 线段树维护,\(a\) 可以使用 xor 版树状数组维护。

  • P4869 albus就是要第一个出场:线性基中的数的子集 xor 和共有 \(2^{|base|}\) 种,剩下的数的子集 xor 和有 \(2^{n - |base|}\) 种,所以这 \(2^{|base|}\) 种数 xor 和实际各自有 \(2^{n - |base|}\) 种方案。然后可以考虑直接二分 / 在线性基上二分,时间复杂度是前者 2log 后者 1log。

  • P4151 [WC2011] 最大XOR和路径:发现最终答案的路径肯定是某个 \(1 \sim n\) 的简单路径再插入几个环,所以我们可以考虑 dfs 出一棵生成树找出所有的环,插入到线性基中,然后查询与 \(1 \sim n\) 的路径 xor 和的最大 xor 和即可。

  • CF1100F Ivan and Burgers:前缀线性基的板子。只需要贪心的加编号大的即可,具体见代码。

这里给出 CF1100F 的代码:

#include <bits/stdc++.h>
#include <ext/pb_ds/assoc_container.hpp>
#include <ext/pb_ds/tree_policy.hpp>
#include <ext/pb_ds/hash_policy.hpp>
#include <ext/rope>
using namespace std;
using namespace __gnu_pbds;
using namespace __gnu_cxx;
#ifdef LOCAL
#include "algo/debug.h"
#else
#define debug(...) 42
#endif
typedef long long ll;
typedef pair < int, int > PII;
typedef int itn;
mt19937 RND_MAKER (chrono :: steady_clock :: now ().time_since_epoch ().count ());
inline ll randomly (const ll l, const ll r) {return (RND_MAKER () ^ (1ull << 63)) % (r - l + 1) + l;}
bool Mbes;
const double pi = acos (-1);
//__gnu_pbds :: tree < Key, Mapped, Cmp_Fn = std :: less < Key >, Tag = rb_tree_tag, Node_Upadte = null_tree_node_update, Allocator = std :: allocator < char > > ;
//__gnu_pbds :: tree < PPS, __gnu_pbds :: null_type, less < PPS >, __gnu_pbds :: rb_tree_tag, __gnu_pbds :: tree_order_statistics_node_update > tr;
inline bool statisqwq (char c) {
  if (c < '0') return true;
  if (c > '9') return true;
  return false;
}
inline int read () {
  int x = 0, f = 0;
  char c = getchar ();
  for ( ; statisqwq (c) ; c = getchar ()) f = (c == '-') ? 1 : f;
  for ( ; c >= '0' && c <= '9' ; c = getchar ()) x = (x << 1) + (x << 3) + (c & 15);
  return !f ? x : -x;
}
inline ll readll () {
  ll x = 0, f = 0;
  char c = getchar ();
  for ( ; statisqwq (c) ; c = getchar ()) f = (c == '-') ? 1 : f;
  for ( ; c >= '0' && c <= '9' ; c = getchar ()) x = (x << 1) + (x << 3) + (c & 15);
  return !f ? x : -x;
}
const ll hashmod1 = 2147483579;
const ll hashmod2 = 1610612741;
const ll hashmod3 = 805306457;
inline void chkmax (int &x, int y) {x = max (x, y);}
inline void chkmin (int &x, int y) {x = min (x, y);}
inline void chkmax2 (ll &x, ll y) {x = max (x, y);}
inline void chkmin2 (ll &x, ll y) {x = min (x, y);}
const int N = 5e5 + 5;
int n, m;
struct linear_basis_awa {
  // If don't use "query_kth",
  // please "//" cy[64], bt[64], build_kth(), query_kth(k) and upd thanks meow!
  int bs[21], aff[21];
  int cnt, num;
  inline void init () {
    for (int i = 0;i < 21; ++ i) bs[i] = aff[i] = 0;
    cnt = 0;
  }
  inline void ins (int x, int can) {
    if (!x) return ;
    for (int i = 20;i >= 0; -- i) {
      if (x & (1 << i)) {
        if (bs[i]) {
          if (can > aff[i]) {
            swap (can, aff[i]);
            swap (x, bs[i]);
          }
          x ^= bs[i];
        }
        else {
          cnt ++;
          bs[i] = x;
          aff[i] = can;
          break;
        }
      }
    }
  }
  inline int query_maxxor (int x) {
    int res = x;
    for (int i = 20;i >= 0; -- i) {
      if ((res ^ bs[i]) > res) res ^= bs[i];
    }
    return res;
  }
  inline void merge_bas (linear_basis_awa O) {
    for (int i = 0;i < 21; ++ i) ins (O.bs[i], O.aff[i]);
  }
  inline void operator = (linear_basis_awa O) {
    for (int i = 0;i < 21; ++ i) bs[i] = O.bs[i], aff[i] = O.aff[i];
    cnt = O.cnt;
  }
} li[N];
int a[N];
bool Meds;
signed main () {
  cerr << (&Mbes - &Meds) / 1048576.0 << " MB\n";
  n = read ();
  for (int i = 1;i <= n; ++ i) {
    a[i] = read ();
    li[i].init ();
    li[i].ins (a[i], i);
    if (i > 1) li[i].merge_bas (li[i - 1]);
  }
  m = read ();
  while (m --) {
    int l = read (), r = read (), ans = 0;
    for (int i = 20;i >= 0; -- i) {
      if (li[r].aff[i] >= l) {
        if ((ans ^ li[r].bs[i]) > ans) ans ^= li[r].bs[i];
      }
    }
    printf ("%d\n", ans);
  }
  return 0;
}
posted @ 2023-08-15 19:06  CountingGroup  阅读(16)  评论(0编辑  收藏  举报