【GJOI 2023.10.14 T4】 莫队

莫队

今天,接触信息学不久的小 A 刚刚学习了莫队。
莫队可以解决一类难以合并,但方便插入的信息维护。比如,给定一个序列,支持单点修改,每次询问一个区间出现了多少种数字。再比如,给定一个序列,支持单点修改,每次询问区间众数。诸如此类。
小 A 觉得这样的情况太平凡了。于是,他定义了一个区间是无重的,当且仅当区间内没有重复的数字。具体的,一个区间 \([l,r]\) 无重,当且仅当 \(\forall l\le i< j \le r\) ,都有 $ a_i\not=a_j $ 。
他给定了一个长度为 \(n\) 序列 \(a\),并给出 \(m\) 组操作:每次修改一个位置的数,或者询问一个区间 \([l,r]\) 中,有多少个无重的子区间,\(n,m \le 10^6\)

解题思路

首先,对于一个数 \(x\) ,它后面第一个和它重复的数定义为 \(nxt_x\)
很明显,对于一个区间中的一个数 \(a\) ,设 \(b\) 为区间左端点,若 \(x \in [a,b]\)\(b < nxt_x\) ,区间 \([a,b]\) 都能满足条件。
就是 \(b<min(nxt_x) ,\) \(x \in [a,b]\)
\(min(nxt_x)\) 实在不断变小的,所以 \(b\) 实在不断变小的。
所以,对于区间 \([l,r]\) 中的一个数 \(a\) ,它能取到的右端点定小于 \(min(nxt_x),x \in [a,r]\)
从左往右看,就是每次往 \(min\) 集合中添一个数,求每次加之后的的最小值减去当前的左端点 \(a\)
提出 \(a\) 来,就是求 \(min(nxt_r)+min(nxt_{r-1},nxt_r)+......+min(nxt_l,......nxt_r)\)
考虑如何维护这个东西。
做一个线段树,维护 \(minn,f\)\(minn\) 就是区间最小值, \(f\) 就是一个区间内的答案。
在线段树外面开个 \(set\) ,用于由原数组来动态维护 \(nxt\)
每次修改 \(nxt\) ,在线段树上修改。
上传时先上传 \(minn\) ,对于 \(f\) ,我们设计一个函数 \(merge(x,l,r,v)\) 来维护一个区间的答案,参数分别代表当前做的区间,区间左端点与右端点,以及 \(min\) 集合中的最小值。
设右儿子区间的 \(minn\)\(x\) ,若 \(x<v\) 说明左儿子区间不会变化,答案为 \(f_x-f_{rc}+merge(rc,mid+1,r,v)\)
否则,右儿子区间内答案为 \((r-mid)*v\) ,左儿子答案为 \(merge(lc,l,mid,v)\)
\(f_x\) 即为 \(f[rc]+merge(lc,l,mid,minn[rc])\)
一次修改时间复杂度 \(O(log^2n)\)
而查询时,我们可以继续利用 \(merge\) 函数,将查询区间按线段树划分方法划成一个个区间,从右往左用 \(merge\) 函数合并,最后就是答案。
减去左端点的和 \((l+r)(r-l+1)/2\) 即可。
一次查询时间复杂度 \(O(log^2n)\) ,总时间复杂度 \(O((n+m)logn)\)

Code

#include<bits/stdc++.h>
#pragma GCC optimize(1)
#pragma GCC optimize(2)
#pragma GCC optimize(3,"Ofast","inline")
using namespace std;
long long n,m,minn[800005],f[800005],s=0,a[200005],nxt[200005],rr;
set<long long> l[200005];
long long merge(long long x,long long l,long long r,long long v)
{
	if(l==r)return min(v,minn[x]);
	long long lc=(x<<1),rc=(x<<1)|1,mid=(l+r)>>1;
	if(minn[rc]<=v)return f[x]-f[rc]+merge(rc,mid+1,r,v); 
	return v*(r-mid)+merge(lc,l,mid,v); 
}
void up(long long x,long long l,long long r)
{
	long long lc=(x<<1),rc=(x<<1)|1,mid=(l+r)>>1;
	minn[x]=min(minn[lc],minn[rc]);
	f[x]=f[rc]+merge(lc,l,mid,minn[rc]);
	return;
}
void dijah(long long x,long long l,long long r,long long k,long long v)
{
	if(l==r)
	{
		minn[x]=v;
		f[x]=v;
		return;
	}
	long long lc=(x<<1),rc=(x<<1)|1,mid=(l+r)>>1;
	if(k<=mid)dijah(lc,l,mid,k,v);
	else dijah(rc,mid+1,r,k,v);
	up(x,l,r);
	return;
}
void gaia(long long x,long long l,long long r,long long ql,long long qr)
{
	if(ql<=l&&r<=qr)
	{
		s+=merge(x,l,r,rr);
		rr=min(rr,minn[x]);
		return;	
	}
	long long lc=(x<<1),rc=(x<<1)|1,mid=(l+r)>>1;
	if(qr>mid)gaia(rc,mid+1,r,ql,qr); 
	if(ql<=mid)gaia(lc,l,mid,ql,qr);
	return;
}
int main()
{
	long long p,x,y,qwe=0;
	scanf("%lld%lld",&n,&m);
	for(int i=1;i<=4*n;i++)f[i]=minn[i]=n+1;
	for(int i=1;i<=n;i++)
	{
		scanf("%lld",&a[i]);
		l[a[i]].insert(i);
	}
	set<long long>::iterator q,w,e;
	for(int i=0;i<=n;i++)
	{
		if(l[i].size()==0)continue;
		q=l[i].begin();
		w=q;
		w++;
		for(;w!=l[i].end();q++,w++)
		{
			dijah(1,1,n,*q,*w);
			nxt[*q]=*w;
		} 
		dijah(1,1,n,*q,n+1);
		nxt[*q]=n+1;
	}
	for(int i=1;i<=m;i++)
	{
		scanf("%lld%lld%lld",&p,&x,&y);
		if(p==1)
		{
			w=l[a[x]].find(x);
			if(w!=l[a[x]].begin())
			{
				q=w;
				e=w;
				q--;
				e++;
				if(e!=l[a[x]].end())
				{
					dijah(1,1,n,*q,*e);
					nxt[*q]=*e;
				}
				else
				{
					nxt[*q]=n+1;
					dijah(1,1,n,*q,n+1);
				}
			}
			l[a[x]].erase(w);
			a[x]=y;
			l[y].insert(x);
			w=l[y].find(x);
			e=w;
			e++;
			if(e!=l[y].end())
			{
				dijah(1,1,n,*w,*e);
				nxt[*w]=*e;
			}
			else
			{
				dijah(1,1,n,*w,n+1);
				nxt[*w]=n+1;
			}
			if(w!=l[y].begin())
			{
				q=w;
				q--;
				dijah(1,1,n,*q,*w); 
				nxt[*q]=*w;
			}
		}
		else 
		{
			if(x>y)swap(x,y);
			rr=y+1;
			s=0;
			gaia(1,1,n,x,y);
			printf("%lld\n",s-(y-x+1)*(x+y)/2);
			s=0;
		}
	}








  return 0;
}
posted @ 2023-10-17 15:46  dijah  阅读(29)  评论(0编辑  收藏  举报