线性基
P3812 【模板】线性基
题目描述
给定 \(n\) 个整数(数字可能重复),求在这些数中选取任意个,使得他们的异或和最大。
提示
\(1 \leq n \leq 50, 0 \leq S_i < 2 ^ {50}\)
注意开long long
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
using i64 = long long;
const int N = 70;
i64 p[N];
void insert(i64 x)
{
for (int i = 63; i >= 0; i--)
{
if (x >> i & 1)
{
if (!p[i])
{
p[i] = x;
break;
}
else
{
x ^= p[i];
}
}
}
}
i64 getans()
{
i64 res = 0;
for (int i = 63; i >= 0; i--)
if ((res ^ p[i]) > res)
res ^= p[i];
return res;
}
int n;
int main()
{
ios::sync_with_stdio(false);
cin.tie(nullptr);
cin >> n;
for (int i = 1; i <= n; i++)
{
i64 x;
cin >> x;
insert(x);
}
cout << getans() << "\n";
return 0;
}
HDU-3949 XOR
P3812 的进阶版。
注意1 << 63
会炸,要使用1ll << 63
或 1ll << 63ll
!
#include <iostream>
#include <cstring>
#include <algorithm>
#include <vector>
using namespace std;
#define int long long
const int N = 70;
int p[N];
inline void insert(int x)
{
for (int i = 63; i >= 0; i--)
{
if (x >> i & 1)
{
if (!p[i])
{
p[i] = x;
break;
}
else x ^= p[i];
}
}
}
inline void solve()
{
int n;
cin >> n;
memset(p, 0, sizeof(p));
for (int i = 1; i <= n; i++)
{
int x;
cin >> x;
insert(x);
}
for (int i = 0; i <= 63; i++)
for (int j = i + 1; j <= 63; j++)
if (p[j] >> i & 1)
p[j] ^= p[i];
vector<int> res;
for (int i = 0; i <= 63; i++) if (p[i]) res.push_back(p[i]);
int sz = res.size();
int q;
cin >> q;
while (q--)
{
int x, ans = 0;
cin >> x;
if (n > sz) x--;
if ((1ll << sz) - 1ll < x) ans = -1;
else for (int i = 63; i >= 0; i--) if (x >> i & 1) ans ^= res[i];
cout << ans << '\n';
}
}
signed main()
{
ios::sync_with_stdio(false);
cin.tie(nullptr);
int T;
cin >> T;
for (int i = 1; i <= T; i++)
{
cout << "Case #" << i << ":\n";
solve();
}
return 0;
}
P3857 [TJOI2008]彩灯
与 HDU-3949 相同,一个是统计第 \(k\) 小,一个是统计个数。
实际上就是 \(p\) 数组中有值的数字个数 \(cnt\) (即 \(\sum[p[i] \neq 0]\)), 答案为 \(2^{cnt}\)。
#include <iostream>
#include <cstring>
#include <algorithm>
#define int long long
using namespace std;
const int N = 64;
int n, m;
int p[N];
void insert(int x)
{
for (int i = 63; i >= 0; i--)
{
if (x >> i & 1)
{
if (!p[i])
{
p[i] = x;
break;
}
else x ^= p[i];
}
}
}
signed main()
{
ios::sync_with_stdio(false);
cin.tie(nullptr);
cin >> m >> n;
for (int i = 1; i <= n; i++)
{
int t = 0;
for (int j = 1; j <= m; j++)
{
char ch;
cin >> ch;
t = t << 1 | (ch == 'O');
}
// cout << t << '\n';
insert(t);
}
for (int i = 0; i <= 63; i++)
for (int j = i + 1; j <= 63; j++)
if (p[j] >> i & 1) p[j] ^= p[i];
int cnt = 0;
for (int i = 0; i <= 63; i++) if (p[i]) cnt++;
cout << (1ll << cnt) % 2008ll << '\n';
return 0;
}
P4570 [BJWC2011]元素
贪心,按 \(Magic_i\) 从大到小排序,再将 \(Number_i\) 插入线性基,如果插入成功,就表示不会发生冲突,答案 \(ans\) 加上 \(Magic_i\);否则跳过。
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
using i64 = long long;
using PII = pair<i64, i64>;
const int N = 1010, M = 64;
i64 p[M];
PII a[N];
int n;
bool insert(i64 x)
{
for (int i = 63; i >= 0; i--)
{
if (x >> i & 1)
{
if (!p[i])
{
p[i] = x;
return true;
}
else x ^= p[i];
}
}
return false;
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(nullptr);
cin >> n;
for (int i = 1; i <= n; i++) cin >> a[i].first >> a[i].second;
sort(a + 1, a + n + 1, [](const PII& x, const PII& y){ return x.second > y.second; });
i64 ans = 0;
for (int i = 1; i <= n; i++)
if (insert(a[i].first))
ans += a[i].second;
cout << ans << '\n';
return 0;
}
CF845G Shortest Path Problem?
这题每一条路径相当于 \(一条链 + 几个环\)。
那么此题的思路为:
-
随便找一条链,链上所有的值异或起来,记为 \(ans\)。
-
在把所有环上的异或值插入线性基。
然后再在线性基中随便找一些数异或 \(ans\),使其最大就行了,成功转化为模板题。
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 100010, M = 200010;
struct Edge
{
int to;
int next;
int w;
}e[M];
int head[N], idx;
void add(int a, int b, int c)
{
idx++, e[idx].to = b, e[idx].next = head[a], e[idx].w = c, head[a] = idx;
idx++, e[idx].to = a, e[idx].next = head[b], e[idx].w = c, head[b] = idx;
}
int n, m;
int sumx[N]; // sumx[i] : 1 ~ i xor
bool vis[N]; // visited ?
int p[70];
void insert(int x)
{
// cout << x << '\n';
for (int i = 31; i >= 0; i--)
{
if (x >> i & 1)
{
if (!p[i])
{
p[i] = x;
break;
}
else x ^= p[i];
}
}
}
void dfs(int u, int x)
{
sumx[u] = x, vis[u] = true;
for (int i = head[u]; i; i = e[i].next)
{
int to = e[i].to;
if (!vis[to]) dfs(to, x ^ e[i].w);
else insert(x ^ e[i].w ^ sumx[to]);
}
}
int main()
{
scanf("%d%d", &n, &m);
for (int i = 1; i <= m; i++)
{
int a, b, c;
scanf("%d%d%d", &a, &b, &c);
add(a, b, c);
}
dfs(1, 0);
int res = sumx[n];
for (int i = 31; i >= 0; i--)
if ((res ^ p[i]) < res)
res ^= p[i];
printf("%d\n", res);
return 0;
}
P3292 [SCOI2016]幸运数字
注意:
-
两个线性基是可合并的,合并可以参考代码中的 \(merge\)。
-
插入线性基的时候最好判断一下该数是否为 \(0\),否则你爆T了。
于是,此题在暴力(30分)的基础上加个 \(LCA+RMQ\) 就可以了。
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
using i64 = long long;
const int N = 20010, M = 40010;
struct Edge
{
int to;
int next;
}e[M];
int head[N], idx;
void add(int a, int b)
{
idx++, e[idx].to = b, e[idx].next = head[a], head[a] = idx;
idx++, e[idx].to = a, e[idx].next = head[b], head[b] = idx;
}
int n, q;
bool vis[N];
i64 p[N][20][64];
int d[N];
int f[N][20];
void insert(i64 x, i64 c[64])
{
for (int i = 63; i >= 0; i--)
{
if (x >> i & 1)
{
if (!c[i])
{
c[i] = x;
break;
}
else x ^= c[i];
}
}
}
void merge(i64 a[64], i64 b[64])
{
for (int i = 63; i >= 0; i--) if (b[i]) insert(b[i], a);
}
void dfs(int u, int fa)
{
d[u] = d[fa] + 1;
f[u][0] = fa;
for (int i = head[u]; i; i = e[i].next)
{
int to = e[i].to;
if (to != fa) dfs(to, u);
}
}
void prepare()
{
for (int j = 1; j < 20; j++)
for (int i = 1; i <= n; i++)
{
f[i][j] = f[f[i][j - 1]][j - 1];
for (int k = 0; k <= 63; k++) p[i][j][k] = p[i][j - 1][k];
merge(p[i][j], p[f[i][j - 1]][j - 1]);
}
}
void lca(int x, int y, i64 res[64])
{
for (int i = 0; i <= 63; i++) res[i] = 0;
if (d[x] < d[y]) swap(x, y);
for (int i = 19; i >= 0; i--)
if (d[f[x][i]] >= d[y])
{
merge(res, p[x][i]);
x = f[x][i];
}
if (x == y)
{
merge(res, p[x][0]);
return;
}
for (int i = 19; i >= 0; i--)
if (f[x][i] != f[y][i])
{
merge(res, p[x][i]);
merge(res, p[y][i]);
x = f[x][i], y = f[y][i];
}
merge(res, p[x][1]);
merge(res, p[y][1]);
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(nullptr);
cin >> n >> q;
for (int i = 1; i <= n; i++)
{
i64 x;
cin >> x;
insert(x, p[i][0]);
}
for (int i = 1; i < n; i++)
{
int a, b;
cin >> a >> b;
add(a, b);
}
dfs(1, 0);
prepare();
i64 tmp[64];
while (q--)
{
int a, b;
cin >> a >> b;
lca(a, b, tmp);
i64 res = 0;
for (int i = 63; i >= 0; i--)
if ((res ^ tmp[i]) > res)
res ^= tmp[i];
cout << res << '\n';
}
return 0;
}