区间更新+线段树

题目当中说的区间更新比较有歧义,其实一共有2种区间更新,一种是内区间更新,还有一种是外区间更新。啥意思?比如说,我们现在说 [ a , b ] [a,b] [a,b]的所有小孩身高都是 165 c m 165cm 165cm,这种就是内区间更新。外区间更新就是:端点 a a a和端点 b b b满足条件,那么如果有 k ≤ a , m ≥ b k\leq a,m\geq b ka,mb,则在 [ k , m ] [k,m] [k,m]中都满足这个条件。
那么对于内区间更新一定是用线段树,那么外区间更新怎么做?
首先先看实际应用场景:
给一个序列 [ a 1 , a 2 , a 3 , . . . , a n ] [a_{1},a_{2},a_{3},...,a{n}] [a1,a2,a3,...,an],问一个闭区间 [ l , r ] [l,r] [l,r]是否存在两个数异或和为 x x x
这个题拿过来想都不用想,直接线性基 ,就应该知道先看都有哪些数异或和为 x x x,但是显然不能 O ( n 2 ) O(n^{2}) O(n2)判断。我们知道一个定理为
a ⊕ b = c , 则 a ⊕ c = b a\oplus b=c,则a\oplus c=b ab=c,ac=b
所以直接线性扫一遍,然后记录各个数的位置,用这种方法去重:

for (int i = 1; i <= n; i++)
	{
		int a;
		scanf_s("%d", &a);
		cnt[a]++;
		edge[a].push_back(i);
		max_a = max(max_a, a);
	}
	vector<int> kl;
	for (int i = 0; i <= max_a; i++)
	{
		if (cnt[i])
			kl.push_back(i);//这块去重
		left1[i] = n + 1;
	}

然后对 k l kl kl这个数组扫一遍, ∀ i , k l [ i ] \forall i,kl[i] i,kl[i]的异或 k l [ i ] ⊕ x kl[i]\oplus x kl[i]x e d g e edge edge数组当中我们都已经记录下来了,所以只需要连边即可。这两个数可以称作一个对,对于每一个左边界,我们只需要知道他的右边界最近是哪,将这个信息存在 l e f t left left数组当中。那么在判断 [ l , r ] [l,r] [l,r]时,我们自然就可以用线段树来判断这个区间内的 l e f t left left的最小值就可以了。如果这个最小值小于 r r r,则 y e s yes yes否则 n o no no.
AC代码:

//区间+并查集才是正解
#include<iostream>
#include<cstdio>
#include<vector>
#include<algorithm>
using namespace std;
const int length = 2e6 + 5;
int cnt[length];
int f[length];
vector<vector<int>> edge(length);
int vis[length];
int tree[length];
int left1[length];
void update(int L, int R, int l, int r, int cnt, int v)
{
	if (l >= L && r <= R)
	{
		tree[cnt]=v;
		return;
	}
	if (l > R || r < L)
		return ;
	int mid = (l + r) / 2;
    update(L, R, l, mid, cnt*2,v);
    update(L, R, mid + 1, r, cnt*2+1,v);
	tree[cnt] = min(tree[cnt * 2], tree[cnt * 2 + 1]);
}
int query(int L, int R, int l, int r, int cnt)
{
	if (l >= L && r <= R)
		return tree[cnt];
	if (l > R || r < L)
		return INT_MAX;
	int mid = (l + r) / 2;
	int a = query(L, R, l, mid, cnt*2);
	int b = query(L, R, mid + 1, r, cnt*2+1);
	return min(a, b);
}
int find1(int x)
{
	return f[x]=(x == f[x]) ? x : find1(f[x]);
}
int main(void)
{
	int n, m, x;
	scanf_s("%d%d%d", &n, &m, &x);
	int max_a = -1;
	for (int i = 1; i <= n; i++)
	{
		int a;
		scanf_s("%d", &a);
		cnt[a]++;
		edge[a].push_back(i);
		max_a = max(max_a, a);
	}
	vector<int> kl;
	for (int i = 0; i <= max_a; i++)
	{
		if (cnt[i])
			kl.push_back(i);//这块去重
		left1[i] = n + 1;
	}
	
	for (int i = 0; i < kl.size(); i++)
	{
		int s = kl[i];
		int t = kl[i] ^ x;
		if (cnt[t] != 0&&!vis[s]&&!vis[t])
		{
			vis[s] = 1;
			vis[t] = 1;
			for (int m : edge[s])
			{
				for (int j : edge[t])
				{
					if (m == j)
						continue;
					int a = m;
					int b = j;
					if (m > j)
					{
						a = j;
						b = m;
					}
					left1[a] = min(left1[a], b);
				}
			}
		}
	}
	for (int i = 1; i <= n; i++)
	{
		update(i, i, 1, n, 1, left1[i]);
	}
	for (int i = 0; i < m; i++)
	{
		int a, b;
		scanf_s("%d%d", &a, &b);
		int a1 = query(a, b-1, 1, n, 1);
		if (a1<=b)
			printf("yes\n");
		else
			printf("no\n");
	}
}

沙雕线段树代码:

//可以用线段树维护一个线性基
//然后每次查询就看相应的子树有没有就可以了
#include<iostream>
#include<cstdio>
int x;
struct s
{
	int o[25];
	void ist(int v)
	{
		for (int i = 24; i >= 0; i--)
		{
			if (v == 0)
				return;
			if (v&(1 << i))
			{
				if (o[i] == 0)
				{
					o[i] = v;
					return;
				}
				v = v ^ o[i];
			}
		}
	}
	void clc()
	{
		memset(o, 0, sizeof(o));
	}
};
const int length = 1e5 + 5;
struct s tree[length<<1];
struct s mrg(struct s &a, struct s &b)
{
	struct s tmp = a;
	for (int i = 24; i >= 0; i--)
	{
		tmp.ist(b.o[i]);
	}
	return tmp;
}
void update(int L, int R, int l, int r, int cnt, int v)
{
	if (l >= L && r <= R)
	{
		tree[cnt].ist(v);
		return;
	}
	if (l > R || r < L)
		return;
	int mid = (l + r) / 2;
	update(L, R, l, mid, cnt * 2, v);
	update(L, R, mid + 1, r, cnt * 2 + 1, v);
	tree[cnt] = mrg(tree[cnt * 2], tree[cnt * 2 + 1]);
}
struct s query(int L, int R, int l, int r, int cnt)
{
	if (l >= L && r <= R)
	{
		return tree[cnt];
	}
	if (l > R || r < L)
	{
		struct s tmp;
		tmp.clc();
		return tmp;
	}
	int mid = (l + r) / 2;
	struct s a=query(L, R, l, mid, cnt * 2);
	struct s b=query(L, R, mid + 1, r, cnt * 2 + 1);
	return mrg(a, b);
}
bool solve(int L, int R, int l, int r, int cnt)
{
	struct s pl= query(L, R, l, r, cnt);
	for (int i = 0; i < 25; i++)
	{
		if (x&(1 << i))
		{
			if (pl.o[i] != 0)
			{
				continue;
			}
			else
				return false;
		}
	}
	return true;
}
int main(void)
{
	int n, m;
	scanf_s("%d%d%d", &n, &m, &x);
	for (int i = 1; i <= n; i++)
	{
		int a;
		scanf_s("%d", &a);
		update(i, i, 1, n, 1,a);
	}
	for (int i = 0; i < m; i++)
	{
		int l, r;
		scanf_s("%d%d", &l, &r);
		bool a=solve(l, r, 1, n, 1);
		//可以经过查询返回一个线性基
		//然后用solve判断x在不在里头
		if (a)
			printf("yes\n");
		else
			printf("no\n");
	}
}

文末彩蛋:我还想到另一种做法,就是用并查集+线段树。将任意一个满足要求的对 < i , j > <i,j> <i,j>,其左区间 [ 1 , i − 1 ] [1,i-1] [1,i1]和右区间 [ j + 1 , n ] [j+1,n] [j+1,n]给并起来,利用线段树存储。这样做是防止有可能 [ 2 , 6 ] [2,6] [2,6]满足, [ 4 , 9 ] [4,9] [4,9]满足,而 [ 3 , 8 ] [3,8] [3,8]不满足(因为在线性基做不出来的时候,我直接想到可以维护一个 l e f t left left r i g h t right right l e f t = m i n ( l e f t , i ) ; r i g h t = m a x ( r i g h t , j ) ) left=min(left,i);right=max(right,j)) left=min(left,i);right=max(right,j))。但是我忘了个事, [ 1 , 2 ] [1,2] [1,2]不满足。并且这样做还有一个问题就是线段树和并查集的 f f f数组不能保持一致。嘤.

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值