不重复区间 - 线段树

给一个数列,每次修改一个数字或者问有多少区间内没有相同的数字的区间。n,m≤105n,m\le10^5n,m105
题解:
首先固定r,相当于l不断变小同时pre的max变大,一直到要max(pre)>=l为止。
考虑类似线段树维护单调栈的做。
每次一个区间的答案等于左区间的答案加上,从右区间开始往左走并考虑左区间对右区间的影响。
考虑在线段树上二分出右区间最长的一个前缀,满足这个前缀的max(pre)<=左区间的max(pre),那么这段区间都等价于从从mid开始往左走(并且显然可以走到mid),对与后半部分,左区间对其没有影响(被这个前缀堵死了),直接用已经算好的东西更新答案即可。

#include<iostream>
#include<cstring>
#include<cstdio>
#include<algorithm>
#include<set>
#define N 100010
#define gc getchar()
#define lint long long
using namespace std;
inline int inn()
{
	int x,ch;while((ch=gc)<'0'||ch>'9');
	x=ch^'0';while((ch=gc)>='0'&&ch<='9')
		x=(x<<1)+(x<<3)+(ch^'0');return x;
}
typedef set<int>::iterator sit;
struct segment{
	int l,r,mx;lint ans;
	segment *ch[2];
}*rt;set<int> s[N];int pre[N],a[N],h[N];
int cut_segment(segment* &rt,segment* &t,int lm)
{
	int l=rt->l,r=rt->r;if(l==r) return t->ans+=l-max(lm,rt->mx),0;int mid=(l+r)>>1;
	if(rt->ch[0]->mx<=lm)
		t->ans+=(mid-l+1ll)*(l-1-lm)+(mid-l+1ll)*(mid-l+2ll)/2,cut_segment(rt->ch[1],t,lm);
	else t->ans+=rt->ans-rt->ch[0]->ans,cut_segment(rt->ch[0],t,lm);return 0;
}
inline int push_up(segment* &rt)
{
	int l=rt->l,r=rt->r,mid=(l+r)>>1,lm=rt->ch[0]->mx;
	rt->mx=max(rt->ch[0]->mx,rt->ch[1]->mx),rt->ans=rt->ch[0]->ans;
	if(rt->ch[0]->mx>=rt->ch[1]->mx) rt->ans+=((lint)mid-lm)*(r-mid)+(r-mid)*(r-mid+1ll)/2;
	else cut_segment(rt->ch[1],rt,lm);return 0;
}
int build(segment* &rt,int l,int r)
{
	rt=new segment,rt->l=l,rt->r=r;int mid=(l+r)>>1;
	if(l==r) return rt->mx=pre[l],rt->ans=l-pre[l],0;
	build(rt->ch[0],l,mid),build(rt->ch[1],mid+1,r);
	return push_up(rt);
}
int update(segment* &rt,int p)
{
	int l=rt->l,r=rt->r,mid=(l+r)>>1;
	if(l==r) return rt->mx=pre[p],rt->ans=p-pre[p],0;
	return update(rt->ch[p>mid],p),push_up(rt);
}
int main()
{
	int n=inn();
	for(int i=1;i<=n;i++) a[i]=inn();
	for(int i=1;i<=n;i++) pre[i]=h[a[i]],h[a[i]]=i;
	for(int i=1;i<=n;i++) s[a[i]].insert(i);
	build(rt,1,n);
	for(int q=inn(),opt;q;q--)
		if(!(opt=inn())) printf("%lld\n",rt->ans);
		else{
			int x=inn(),v=inn();
			set<int> &s1=s[a[x]],&s2=s[v];
			sit it=s1.upper_bound(x),its;
			if(it!=s1.end())
				pre[*it]=pre[x],update(rt,*it);
			s1.erase(x),a[x]=v,s2.insert(x);
			its=it=s2.lower_bound(x),it++;
			if(it!=s2.end()) pre[*it]=x,update(rt,*it);
			if(its!=s2.begin()) its--,pre[x]=*its;
			else pre[x]=0;update(rt,x);
		}
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值