*题解:P5278 算术天才⑨与等差数列

原题链接

解析

要想放到线段树上做,就要考虑如何在不排序的情况下通过一系列可合并的信息判别等差数列。对于一个数列,我们知道它的长度 \(len\),配合上最大值 \(mx\) 最小值 \(mn\) 就可以判断询问给出的 \(k\) 能否作为公差,其能作为公差当且仅当 \(mx=mn+(len - 1)\cdot k\);公差为 \(k\) 意味着每个相邻元素的差是 \(k\) 的倍数,也即差的最大公约数是 \(k\) 的倍数;只有这些限制还不够,我们发现可能会出现数列中有相同元素的情况,所以对于每个元素维护在此之前最后出现的位置,由于有修改且强制在线,我们需要 map 套 set 维护每个值的出现位置集合。

上述条件是数列为合法等差数列的充要条件,感性理解一下,在已经确定了上下界和公差后,相邻元素的差是 \(k\) 的倍数保证了数列中的元素所代表的多重集合为 \(A = \{mn + n_1k,mn+n_2k,mn+n_3k,...,mn+n_{len}k\}\),而保证了没有重复元素后,由于我们确定了最小最大值且集合大小不变,可以确定 \(n\) 的取值覆盖了 \(0\)\(len-1\)似乎不是很感性。

综上,只需在线段树上维护区间最大最小值,区间最左最右值(用于求相邻元素的差),区间元素此前最后出现位置即可解决此题。

时间复杂度 \(O(m\log^2 n)\)

代码

注意公差为 \(0\) 和单个元素的情况。

#include <bits/stdc++.h>
#define ls(p) ((p) << 1)
#define rs(p) (((p) << 1) | 1)
#define mid ((l + r) >> 1)
using namespace std;
typedef long long ll;
typedef pair<int,int> pii;
const int N = 3e5 + 5,M = 500 * 500,mod = 998244353;
int a[N],pre[N];
struct S{
	int empty;
	int mn,mx,lf,ri,gcd,pos;
	S(){
		empty = 1;
		mn = 0,mx = 0,lf = 0,ri = 0,gcd = 0,pos = 0;
	}
	void print(){
		cout<<"--------------------------------------\n";
		cout<<empty<<" "<<mn<<" "<<mx<<" "<<lf<<" "<<ri<<" "<<gcd<<" "<<pos<<'\n';
		cout<<"--------------------------------------\n";
	} 
}tr[N << 2];
map<int,set<int> > mp;
int _gcd(int a,int b){
	if(!a) return b;
	if(!b) return a;
	return __gcd(a,b);
}
S merge(S a,S b){
	S res;
	if(a.empty) return b;
	if(b.empty) return a;
	res.empty = 0;
	res.mn = min(a.mn,b.mn);
	res.mx = max(a.mx,b.mx);
	res.lf = a.lf;
	res.ri = b.ri;
	res.gcd = _gcd(_gcd(a.gcd,b.gcd),abs(a.ri - b.lf));
	res.pos = max(a.pos,b.pos);
	return res;
}
void push_up(int p){
	tr[p] = merge(tr[ls(p)],tr[rs(p)]);
}
void build(int p,int l,int r){
	if(l == r){
		tr[p].empty = false;
		tr[p].mn = tr[p].mx = tr[p].lf = tr[p].ri = a[l];
		tr[p].gcd = 0;
		tr[p].pos = pre[l];
		return;
	}
	build(ls(p),l,mid),build(rs(p),mid + 1,r);
	push_up(p);
}
void modi(int p,int l,int r,int x,int k){
	if(l > x || r < x) return;
	if(l == r){
		tr[p].mn = tr[p].mx = tr[p].lf = tr[p].ri = k;
		tr[p].pos = pre[l];
		return;
	} 
	modi(ls(p),l,mid,x,k),modi(rs(p),mid + 1,r,x,k);
	push_up(p);
}
S query(int p,int l,int r,int L,int R,int k){
	if(l > R || r < L) return S();
	if(l >= L && r <= R){
		return tr[p];
	}
	return merge(query(ls(p),l,mid,L,R,k),query(rs(p),mid + 1,r,L,R,k));
}
int main(){
	ios::sync_with_stdio(false);
	cin.tie(0);
//	freopen("in.txt","r",stdin);
//	freopen("out.txt","w",stdout);
	int n,m;
	cin>>n>>m;
	for(int i=1;i<=n;i++){
		cin>>a[i];
		pre[i] = mp[a[i]].empty() ? 0 : *prev(mp[a[i]].end());
		mp[a[i]].insert(i);
	}
	build(1,1,n);
	int cnt = 0;
	int cntq = 0;
	while(m--){
		int op;
		cin>>op;
		if(op == 1){
			int x,y;
			cin>>x>>y;
			x ^= cnt,y ^= cnt;
			auto it = mp[a[x]].lower_bound(x);//删除及插入时,需修改后继信息并在线段树中更新,修改过程类似链表(其实就是)
			auto it2 = mp[a[x]].upper_bound(x);
			if(it2 != mp[a[x]].end()){
				pre[*it2] = pre[x];
				modi(1,1,n,*it2,a[*it2]);
			}
			mp[a[x]].erase(it);
			it = mp[y].lower_bound(x);
			pre[x] = it == mp[y].begin() ? 0 : *prev(it);
			if(it != mp[y].end()){
				pre[*it] = x;
				modi(1,1,n,*it,a[*it]);
			}
			mp[y].insert(x);
			a[x] = y;
			modi(1,1,n,x,y);
		}else{
			cntq++;
			int l,r,k;
			cin>>l>>r>>k;
			l ^= cnt,r ^= cnt,k ^= cnt;
			if(l == r){
				cout<<"Yes\n";
				cnt++;
				continue;
			}
			S q = query(1,1,n,l,r,k);
			bool f = !k && !q.gcd || k && q.mx == q.mn + 1ll * (r - l) * k && q.gcd % k == 0 && q.pos < l;
			cout<<(f ? "Yes" : "No")<<'\n';
			cnt += f;
		}
	}
	return 0;
}
posted @ 2025-11-11 21:21  yutar  阅读(9)  评论(0)    收藏  举报