Luogu P5556 圣剑护符(线性基,树链剖分,线段树)
Posted 繁凡さん
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Luogu P5556 圣剑护符(线性基,树链剖分,线段树)相关的知识,希望对你有一定的参考价值。
整理的算法模板合集: ACM模板
实际上是一个全新的精炼模板整合计划
Problem
小L 和 小K 面前的圣剑由 n n n 块护符组成,分别编号为 1 , 2 , … , n 1,2,\\ldots , n 1,2,…,n ,有 n − 1 n-1 n−1 条咒力线连接两块护符,形成了一个树形结构。
经过 小L 和 小K 的长时间的研究,他们发现护符之间的相互作用并不复杂。每块护符都有一个属性值,第 i i i 块护符的属性值记为 v i v_i vi 。这个值的每个二进制位上的 0 0 0 或 1 1 1 表示这块护符是否拥有特定属性。所有属性值中相同的二进制位对应的是相同的属性。
对于一系列护符(护符的集合),对于每种特定属性,统计其中包含这一属性的护符数量,如果为偶数,则这一系列护符形成了干涉,最终的属性值对应的二进制位上为 0 0 0 ,如果为奇数则干涉后剩下了一块护符的影响,对应的二进制位为 1 1 1 。也就是说, 护符集合的属性值为单个护符的属性值的异或和 。 空集的属性值定义为 0 0 0 。
现在,小L想知道,如果取出两块护符 x , y x,y x,y 间的简单路径上的所有护符,能否找到两个不相等的子集,使得两个子集的属性值相同(注意到空集也是路径上所有护符集合的子集)。同时,小K会将两块护符间的路径上的所有护符取出进行调整,将所有这些护符的属性值在某些相同二进制位上进行修改(即 0 0 0 变为 1 1 1 , 1 1 1 变为 0 0 0 ),可以看做是将所有这些护符的属性值异或上了一个值。
1 ≤ n , q ≤ 1 0 5 , 1 ≤ x , y ≤ n , 0 ≤ v i , z < 2 30 1\\le n,q\\le 10^5,1\\le x,y\\le n,0\\le v_i,z< 2^{30} 1≤n,q≤105,1≤x,y≤n,0≤vi,z<230
Solution
题目比较长,简而言之就是对于任意路径,看做一个数列,我们可以从中选取两个子集,这两个子集的异或和相等,我们显然可以将这两个异或和相等的自己再次异或,得到一个异或和为
0
0
0 的子集,由于路径上一定存在空集,空集异或和为
0
0
0 ,该异或和为
0
0
0 的子集就与空集配对,输出 YES
,也就是说题目所求是,是否存在一个非空子集,且该子集的异或和为
0
0
0 。
考虑求异或和显然可以使用线性基。我们对于一条路径,我们可以求出它的线性基,就可以快速求得他们能够异或得到的值。
对于路径上的值,一个一个添加至线性基中,对于当前的值
v
x
v_x
vx,若加入后出现某子集的异或和为
0
0
0,说明加入
v
x
v_x
vx 前的线性基可以异或线性表出
v
x
v_x
vx,此时我们使用线性基 insert
v
x
v_x
vx 就会失败,直接输出 YES
即可。
考虑树上路径问题,以及路径修改问题,可以使用树链剖分 + 线段树解决,但是发现时间复杂度为 O ( n log 5 v ) O(n\\log^5 v) O(nlog5v),考虑能否优化。
我们发现输入的数据
v
i
≤
2
30
v_i\\le 2^{30}
vi≤230,也就意味着线性基的维度不超过
30
30
30,假设一条路径上有
n
u
m
≥
30
num\\ge30
num≥30 个点,每次插入一个点,假设他们都线性无关,也最多能插入
30
30
30 个点,超过
30
30
30 个点,开始的
30
30
30 个点由于线性无关组成了线性基,新加入的点一定能够被线性表出,直接输出 YES
即可。若前面插入的并不是全部线性无关,显然也直接输出 YES
即可。
因此我们只需要在路径长度小于
30
30
30 的时候暴力做树链剖分,大于
30
30
30 的时候直接输出 YES
即可。
时间复杂度 O ( n log 2 n ) O(n\\log^2 n) O(nlog2n)
Code
#include <bits/stdc++.h>
using namespace std;
#define lowbit(x) (x & (-x))
const int maxn = 1e5 + 7, maxm = maxn << 1 | 7;
int n, m, s, t, ans;
int head[maxn], edge[maxm], nex[maxm], ver[maxm], tot;
int hson[maxn];
int depth[maxn];
int siz[maxn];
int fa[maxn];
int dfn[maxn], idx;
int a_after[maxn];
int top[maxn];
int id[maxn];
bool flag;
bool vis[maxn];
int v[maxn];
int q;
struct Tree
{
int l, r;
int num;
int laz;
}tr[maxn << 2];
void pushdown(int p)
{
if(tr[p].laz) {
tr[p << 1].laz ^= tr[p].laz;
tr[p << 1].num ^= tr[p].laz;
tr[p << 1 | 1].laz ^= tr[p].laz;
tr[p << 1 | 1].num ^= tr[p].laz;
tr[p].laz = 0;
}
}
void modify(int p, int l, int r, int v)
{
if(tr[p].l > r || tr[p].r < l) return ;
if(tr[p].l >= l && tr[p].r <= r) {
tr[p].num ^= v;
tr[p].laz ^= v;
return ;
}
pushdown(p);
modify(p << 1, l, r, v);
modify(p << 1 | 1, l, r, v);
}
int query(int p, int pos)
{
if(tr[p].l == tr[p].r) return tr[p].num;
pushdown(p);
int mid = tr[p].l + tr[p].r >> 1;
if(pos <= mid) return query(p << 1, pos);
return query(p << 1 | 1, pos);
}
/*void print(int p)
{
if(tr[p].l == tr[p].r) printf("%d ", tr[p].num);
else {
pushdown(p);
print(p << 1);
print(p << 1 | 1);
}
}*/
void build(int p, int l, int r)
{
tr[p].l = l, tr[p].r = r;
tr[p].num = 0, tr[p].laz = 0;
if(l == r) return ;
int mid = l + r >> 1;
build(p << 1, l, mid);
build(p << 1 | 1, mid + 1, r);
}
void add(int x, int y)
{
ver[tot] = y;
nex[tot] = head[x];
head[x] = tot ++ ;
}
void dfs1(int x, int father, int depths)
{
siz[x] = 1;
fa[x] = father;
depth[x] = depths;
int max_son_size = -1;
for (int i = head[x]; ~i; i = nex[i]) {
int y = ver[i];
if(y == father) continue;
dfs1(y, x, depths + 1);
siz[x] += siz[y];
if(siz[y] > max_son_size)
hson[x] = y, max_son_size = siz[y];
}
}
void dfs2(int x, int topfa)
{
dfn[x] = ++ idx;
id[idx] = x;
top[x] = topfa;
modify(1, idx, idx, v[x]);
if(hson[x] == 0) return ;
dfs2(hson[x], topfa);
for (int i = head[x]; ~i; i = nex[i]) {
int y = ver[i];
if(y == fa[x] || y == hson[x]) continue;
dfs2(y, y);
}
return ;
}
int get_lca(int x, int y)
{
int fx = top[x], fy = top[y];
while(fx != fy) {
if(depth[fx] >= depth[fy])
x = fa[fx];
else y = fa[fy];
fx = top[x], fy = top[y];
}
if(depth[x] <= depth[y]) return x;
return y;
}
struct leaner_basis{
int b[31];
void init(){
memset(b, 0, sizeof b);
}
bool insert(int x){
for(int i = 31 - 1;i >= 0; -- i){
if((x & (1 << i)) == 0)
continue;
if(b[i] == 0){
b[i] = x;
return true;
}
x ^= b[i];
}
return false;
}
}B;
bool work(int lca, int x, int y)
{
B.init();
if(B.insert(query(1, id[lca])) == 0)
return true;
while(x != lca)
if(B.insert(query(1, id[x])) == 0)
return true;
else x = fa[x];
while(y != lca)
if(B.insert(query(1, id[y])) == 0)
return true;
else y = fa[y];
return false;
}
void update(int x, int y, int z)
{
while(top[x] != top[y]) {
if(depth[top[x]] < depth[top[y]])
swap(x, y);
modify以上是关于Luogu P5556 圣剑护符(线性基,树链剖分,线段树)的主要内容,如果未能解决你的问题,请参考以下文章
bzoj 4568: [Scoi2016]幸运数字树链剖分+线段树+线性基