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 n1 条咒力线连接两块护符,形成了一个树形结构。

经过 小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} 1n,q105,1x,yn,0vi,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} vi230,也就意味着线性基的维度不超过 30 30 30,假设一条路径上有 n u m ≥ 30 num\\ge30 num30 个点,每次插入一个点,假设他们都线性无关,也最多能插入 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幸运数字 树链剖分 + 线性基合并

bzoj 4568: [Scoi2016]幸运数字树链剖分+线段树+线性基

BZOJ 4568 4568: [Scoi2016]幸运数字 (线性基+树链剖分+线段树)

树链剖分模板题(luogu3384 模板树链剖分)

Luogu P3384树链剖分模板

Luogu P3384 模板树链剖分