查找 Search 解题报告

本文探讨了一种高效处理查找问题的方法,通过维护序列中互补数对的后继关系,解决了给定序列中特定区间内是否存在互补数对的问题。文章首先介绍了类似问题的解决方案,然后详细解释了如何针对'互补后继'进行分类讨论和代码实现,最终展示了在限制条件下维护这一特性的重要性。
摘要由CSDN通过智能技术生成

P6617 查找 Search

题目链接

查找 Search

题目大意

给一个长度为 n n n 的序列 a i a_i ai 和一个参数 k k k ,有 m m m 次操作。操作有两种:

  1. 单点修改序列。
  2. 给定一个区间 [ l , r ] [l,r] [l,r] ,询问是否存在 l ≤ i < j ≤ r l\le i<j\le r li<jr,满足 a i + a j = w a_i +a_j=w ai+aj=w
  • 1 ≤ n , m , w ≤ 5 × 1 0 5 , 0 ≤ a i ≤ w 1\le n,m,w\le5\times 10^5,0\le a_i\le w 1n,m,w5×105,0aiw.
  • 强制在线.

解题报告

我们先来看一个前置知识:

PART 1

看这样一个小题目:

给定一个长度为 n n n 的序列 a i a_i ai ,两种操作:

  1. 单点修改。
  2. 查询区间是否有重复元素。

1 ≤ n ≤ 5 × 1 0 5 1\le n\le 5\times 10^5 1n5×105.

怎么解决?

套路性地维护每个数的后继。改为判断区间内是否 存在 一个数的后继亦在区间内。根据高中数学的知识,这是一种“存在问题”,我们可以转而判断区间后继的 最小值 是否在区间内。如何单点修改?只要用个set数组(下标为权值)记录每个值所有的下标,然后求前驱后继即可。

PART 2

好,我们回到正题。

为了描述方便,我们把“两个数相加为 w w w ”称为这两个数“互补”。

这道题怎么做?可以套路性地维护每个数的后缀中第一个与它互补的数,即互补意义下的“后继”。这样只要判断区间后继最小值即可完成操作2了。

完结撒花!(?)

猛地发现,这样根本维护不来。这个“互补后继”没有之前那道题的“相等后继”那么好的性质。可能有多个数的“互补后继”是同一个数,所以你可能需要改一连串的数。爆炸

根本上的原因是它不满足 后继的后继仍然是它的后继这样一个性质,我们就不能在单点修改时做到仅修改两个数(一个前驱一个后继),这样就没法维护了。

一种绝妙的方法是:不要看这么远。

举个例子:

w = 7
1 3 2 3 4 6

位于第二、四位的3显然可以与4配对。但是,我只让第四位的3与4配对,第三位的3由于前面有另一个3的阻挡,不会与那个4配对。我在说啥

**换句话说:**一个数的后继只会在 一段区间 内查找,中间不能跨过与它相等的数。

容易发现这样更好维护,并且对答案毫无影响。

于是剩下的工作就是分类讨论了。注意细节。

代码详情

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<set>
using namespace std;
typedef long long ll;
char In[1 << 20], *ss = In, *tt = In;
#define getchar() (ss == tt && (tt = (ss = In) + fread(In, 1, 1 << 20, stdin), ss == tt) ? EOF : *ss++)
ll read() {
	ll x = 0, f = 1; char ch = getchar();
	for(; ch < '0' || ch > '9'; ch = getchar()) if(ch == '-') f = -1;
	for(; ch >= '0' && ch <= '9'; ch = getchar()) x = x * 10 + int(ch - '0');
	return x * f;
}
typedef set<int> SET;
const int MAXN = 5e5 + 5;
const int INF = 0x3f3f3f3f;
int n, m, w, lans, f[MAXN], num[MAXN];
namespace Segtree {
#define ls o << 1
#define rs o << 1 | 1
	int val[MAXN << 2];
	void pushup(int o) {val[o] = min(val[ls], val[rs]);}
	void build(int o, int l, int r, int f[]) {
		if(l == r) {val[o] = f[l]; return ;}
		int m = (l + r) >> 1;
		build(ls, l, m, f); build(rs, m+1, r, f);
		pushup(o);
	}
	void mdypos(int o, int l, int r, int x, int v) {
		if(l == r) {val[o] = v; return ;}
		int m = (l + r) >> 1;
		if(x <= m) mdypos(ls, l, m, x, v);
		else mdypos(rs, m+1, r, x, v);
		pushup(o);
	}
	int query(int o, int l, int r, int x, int y) {
		if(x <= l && r <= y) return val[o];
		int m = (l + r) >> 1;
		if(y <= m) return query(ls, l, m, x, y);
		else if(x > m) return query(rs, m+1, r, x, y);
		else return min(query(ls, l, m, x, y), query(rs, m+1, r, x, y));
	}
#undef ls
#undef rs
}
using namespace Segtree;
namespace Settree {
	SET s[MAXN];
	int a[MAXN];
	void ins(int v, int i) {
		s[v].insert(i);
	}
	void del(int v, int i) {
		s[v].erase(i);
	}
	void init() {
		for(int i = 1; i <= n; i++)
			ins(a[i], i);
	}
	void mdy(int i, int v) {
		del(a[i], i);
		a[i] = v;
		ins(a[i], i);
	}
	int pre1(int i) {//sameval
		SET::iterator it = s[a[i]].lower_bound(i);
		if(it == s[a[i]].begin()) return INF;
		return *--it;
	}
	int nxt1(int i) {//sameval
		SET::iterator it = s[a[i]].upper_bound(i);
		if(it == s[a[i]].end()) return INF;
		return *it;
	}
	int pre2(int i) {//buval
		SET::iterator it = s[w-a[i]].lower_bound(i);
		if(it == s[w-a[i]].begin()) return INF;
		return *--it;
	}
	int nxt2(int i) {
		SET::iterator it = s[w-a[i]].upper_bound(i);
		if(it == s[w-a[i]].end()) return INF;
		return *it;
	}
}
using namespace Settree;
void gao1(int i, int v) {
	int p1 = pre1(i), p2 = pre2(i);
	if(p1 != INF) {
		if(p2 == INF || p2 <= p1)
			mdypos(1, 1, n, p1, query(1, 1, n, i, i));
	}
	if(p2 != INF) {
		if(p1 == INF || p1 <= p2)
			mdypos(1, 1, n, p2, nxt1(i));
	}
	mdy(i, v);
	p1 = pre1(i), p2 = pre2(i);
	if(p1 != INF) {
		if(p2 == INF || p2 <= p1)
			mdypos(1, 1, n, p1, INF);
	}
	if(p2 != INF) {
		if(p1 == INF || p1 <= p2)
			mdypos(1, 1, n, p2, i);
	}
	int n1 = nxt1(i), n2 = nxt2(i);
	if(n2 == INF) mdypos(1, 1, n, i, INF);
	else {
		if(n2 <= n1) mdypos(1, 1, n, i, n2);
		else mdypos(1, 1, n, i, INF);
	}

}
bool gao2(int l, int r) {
	return query(1, 1, n, l, r) <= r;
}
int main() {
	n = read(), m = read(), w = read();
	for(int i = 1; i <= n; i++) a[i] = read();
	init();
	for(int i = n; i >= 1; i--) {
		if(num[w-a[i]]) {
			if(!num[a[i]] || num[w-a[i]] <= num[a[i]]) f[i] = num[w-a[i]];
			else f[i] = INF;
		} else f[i] = INF;
		num[a[i]] = i;
	}
	build(1, 1, n, f);
	for(int i = 1; i <= m; i++) {
		int opt = read(), x = read(), y = read();
		if(opt == 1) {
			gao1(x, y);
		} else {
			if(gao2(x ^ lans, y ^ lans)) {
				printf("Yes\n");
				lans++;
			} else {
				printf("No\n");
			}
		}
	}
	return 0;	
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

日居月诸Rijuyuezhu

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值