一道有趣的线段树题目

发布时间 2023-10-11 16:18:10作者: daduoli

\(T4\) 莫队

image

image

image

首先我们需要知道一种统计答案的方法。

我们记 \(R_i\) 表示右边第一个和他相同的位置。

那么我们记 \(a_i=\min(a_{i+1},R_i)\) ,那么贡献就是 \(a_i-i+1\) ,所以我们最后就是要维护 \(a_i\) 就好了。

但是实际上如果你要直接维护 \(a_i\) 没有任何性质是做不了的。

所以我们只能考虑另外一种答案的贡献方式。

考虑一个 \(R_i\) 可以贡献给哪些位置。

显然是他前面一段以他 \(i\) 的后缀 \(\min\) 大于他的数,容易发现这个东西具有单调性,可以使用线段树二分,而对于合并子区间的时候,将右边区间的最小值丢到左边去,看一下能影响多少。对于小于他的数,就用原数,否则改成右区间的最小值。

具体实现,在 \(push\_up\) 的时候改一下就好了。

这样我们每个区间相当于是以当前区间的右端点开始后缀 \(\min\) 的贡献。

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

启示:

关于要我们维护递增或递减关系的信息时,可以先对于每个子区间求出答案,然后在合并区间时再根据单调性用 \(\log\) 的复杂度更新,总复杂度 \(\log^2 n\) 每次操作。

点击查看代码
#include<bits/stdc++.h>
typedef long long LL;

using namespace std;
const int MAXN=2e5+10;
int n,m;
int a[MAXN],ys[MAXN];
set<int> s[MAXN];
struct data_structure {
	LL x,ans,mi;
}tr[(MAXN<<2)];
void ycl() {
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;++i) {
		s[i].insert(n+1);
		scanf("%d",&a[i]);
		s[a[i]].insert(i);
	}
	for(int i=1;i<=n;++i) {
		auto it=s[a[i]].upper_bound(i);
		ys[i]=*it;
	}
}
LL merge(int u,int l,int r,LL x) {
	if(l==r) return min(tr[u].mi,x);
	int mid=(l+r)/2;
	if(tr[(u<<1|1)].mi<=x) return tr[u].ans-tr[(u<<1|1)].ans+merge((u<<1|1),mid+1,r,x);
	return (r-mid)*x+merge((u<<1),l,mid,x);
}
void psup(int u,int l,int mid,int r) {
	tr[u].mi=min(tr[(u<<1)].mi,tr[(u<<1|1)].mi);
	tr[u].ans=merge((u<<1),l,mid,tr[(u<<1|1)].mi)+tr[(u<<1|1)].ans;
}
void build(int u,int l,int r) {
	if(l==r) {
		tr[u].x=tr[u].mi=tr[u].ans=ys[l];
		return ;
	}
	int mid=(l+r)/2;
	build((u<<1),l,mid);
	build((u<<1|1),mid+1,r);
	psup(u,l,mid,r);
}
void update(int u,int l,int r,int x,int y) {
	if(l==r) {
		tr[u].x=tr[u].mi=tr[u].ans=y;
		return ;
	}
	int mid=(l+r)/2;
	if(x<=mid) update((u<<1),l,mid,x,y);
	else update((u<<1|1),mid+1,r,x,y);
	psup(u,l,mid,r);
}
LL ans,lim;
void query(int u,int l,int r,int x,int y) {
	if(l>y||r<x) return ;
	if(l>=x&&r<=y) {
		ans+=merge(u,l,r,lim);
		lim=min(lim,tr[u].mi);
		return ;
	}
	int mid=(l+r)/2;
	query((u<<1|1),mid+1,r,x,y);
	query((u<<1),l,mid,x,y);
}
int main () {
	freopen("team.in","r",stdin);
	freopen("team.out","w",stdout);
	ycl();
	build(1,1,n);
	for(int i=1;i<=m;++i) {
		LL opt,x,y;
		scanf("%lld%lld%lld",&opt,&x,&y);
		if(opt==1) {
			//del a_x
			s[a[x]].erase(x);
			auto it=s[a[x]].upper_bound(x);
			if(it!=s[a[x]].begin()) {
				--it;
				auto itt=s[a[x]].upper_bound(*it);
				update(1,1,n,*it,*itt);
			}
			//insert y
			a[x]=y;
			it=s[a[x]].upper_bound(x);
			if(it!=s[a[x]].end()) update(1,1,n,x,*it);
			if(it!=s[a[x]].begin()) {
				--it;
				update(1,1,n,*it,x);
			}
			s[a[x]].insert(x);
		}
		else {
			ans=0;
			lim=y+1;
			query(1,1,n,x,y);
			ans-=(x+y)*(y-x+1)/2;
			printf("%lld\n",ans);
		}
	}
	return 0;
}