您的位置:首页 > 运维架构

BZOJ4568 SCOI2016 幸运数字 倍增的思想维护线性基(线性基详解)

2016-07-15 22:44 323 查看

题目大意

给你一颗N个节点的树,每个节点都有一个权值Ai,现在有M组询问,每组询问有3个数u,v,,要求你输出在树上节点u到节点v的路径上,每个节点的权值可以选或不选,求选出的点的权值的异或最大值。

N≤2∗104

M≤2∗105

Ai≤260

解题思路

看到这种区间异或最大值的题,那么我们就要联想到线性基,是一个只用维护Log(maxAi)个值就可以表示出区间所有的值的算法,并且合并是Log(maxAi)2。

那么如果会线性基后,就可以先预处理出数组F[i][j]表示从i这个节点到以i向上2j个节点的这段区间的线性基,然后对于每个询问,有倍增的思想来合并预处理好的线性基就可以了。

那么现在的问题就是什么是线性基?

线性基

主要思想

线性基是一个主要解决有关异或方面的问题。它的主要思想就是用尽可能少的数表示出集合中的所有数。根据定义我们就可以得出线性基的几个性质。

1. 对于集合的每一个数都可以有线性基中的是异或而来。

2. 线性基中的每个数都不可以有线性基中的其他数异或而来。

具体操作

实际上对于每组线性基只需要在二进制上的每一位存一个数就可以了。这个很好理解,这种思想跟高斯消元很像,因为每一个第i位为1的数都可以与集合中其他第i为1的数异或,操作一次后,就只剩下当前数的这一位为1,所以说一个线性基只用存Log(maxAi)个数。

合并两个线性基

合并两个线性基时只需把其中一个线性基的数暴力加到另一个中就可以了。加入时的操作也跟高斯消元类似,我们首先需要判断的就是当前这个数能否被另一个线性基中的数表示。加入当前待加入的这个数的第i位为1,那么我们就要判断另一个线性基中的第i位有没有存下数字。如果有那么这一位就可以被消去,如果没有那么这个数就不能被另一个线性基表示,所以在第i位加入这个数。所以合并两个线性基的复杂度是O(Log(maxAi)2)的。

程序

//YxuanwKeith
#include <cstring>
#include <cstdio>
#include <algorithm>

using namespace std;
typedef long long LL;

const int MAXN = 2e4 + 5;

LL F[MAXN][16][65], Ans[65];
int N, M, Deep[MAXN], Fa[MAXN][16];
int tot, Last[MAXN], Next[MAXN * 2], Go[MAXN * 2];

void Link(int u, int v) {
Next[++ tot] = Last[u], Last[u] = tot, Go[tot] = v;
}

void Merge(LL *F, LL Now) {
for (int i = 60; i + 1; i --) {
if ((Now >> i) & 1ll) {
if (!F[i]) {
F[i] = Now;
return;
} else Now ^= F[i];
}
}
}

void MergeAll(LL *F, LL *G) {
for (int i = 60; i + 1; i --)
if (G[i]) Merge(F, G[i]);
}

void Dfs(int Now, int Pre) {
Deep[Now] = Deep[Pre] + 1;
Fa[Now][0] = Pre;
for (int p = Last[Now]; p; p = Next[p])
if (Go[p] != Pre) Dfs(Go[p], Now);
}

void Prepare() {
for (int j = 1; j <= 15; j ++)
for (int i = 1; i <= N; i ++) {
Fa[i][j] = Fa[Fa[i][j - 1]][j - 1];
memcpy(F[i][j], F[i][j - 1], sizeof F[i][j - 1]);
MergeAll(F[i][j], F[Fa[i][j - 1]][j - 1]);
}
}

void Lca(int x, int y) {
if (Deep[x] < Deep[y]) swap(x, y);
for (int i = 15; i + 1; i --)
if (Deep[Fa[x][i]] >= Deep[y]) {
MergeAll(Ans, F[x][i]);
x = Fa[x][i];
}
if (x == y) {
MergeAll(Ans, F[x][0]);
return;
}
for (int i = 15; i + 1; i --)
if (Fa[x][i] != Fa[y][i]) {
MergeAll(Ans, F[x][i]), MergeAll(Ans, F[y][i]);
x = Fa[x][i], y = Fa[y][i];
}
MergeAll(Ans, F[x][0]), MergeAll(Ans, F[y][0]);
MergeAll(Ans, F[Fa[x][0]][0]);
}

void Solve() {
for (int i = 1; i <= M; i ++) {
memset(Ans, 0, sizeof Ans);
int u, v;
scanf("%d%d", &u, &v);
Lca(u, v);
LL Sum = 0;
for (int j = 60; j + 1; j --) Sum = max(Sum, Sum ^ Ans[j]);
printf("%lld\n", Sum);
}
}

int main() {
scanf("%d%d", &N, &M);
for (int i = 1; i <= N; i ++) {
LL Now;
scanf("%lld", &Now);
Merge(F[i][0], Now);
}
for (int i = 1; i < N; i ++) {
int u, v;
scanf("%d%d", &u, &v);
Link(u, v), Link(v, u);
}

Dfs(1, 0);
Prepare();
Solve();
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: