莫队基础题

WC2013 糖果公园

Candyland 有一座糖果公园,公园里不仅有美丽的风景、好玩的游乐项目,还有许多免费糖果的发放点,这引来了许多贪吃的小朋友来糖果公园玩。

糖果公园的结构十分奇特,它由 \(n\) 个游览点构成,每个游览点都有一个糖果发放处,我们可以依次将游览点编号为 \(1\)\(n\)。有 \(n - 1\) 条双向道路连接着这些游览点,并且整个糖果公园都是连通的,即从任何一个游览点出发都可以通过这些道路到达公园里的所有其它游览点。

糖果公园所发放的糖果种类非常丰富,总共 \(m\) 种,它们的编号依次为 \(1\)\(m\)。每一个糖果发放处都只发放某种特定的糖果,我们用 \(c_i\) 来表示 \(i\) 号游览点的糖果。

来到公园里游玩的游客都不喜欢走回头路,他们总是从某个特定的游览点出发前往另一个特定的游览点,并游览途中的景点,这条路线一定是唯一的。他们经过每个游览点,都可以品尝到一颗对应种类的糖果。

大家对不同类型的糖果的喜爱程度都不尽相同。根据游客们的反馈打分,我们得到了糖果的美味指数,第 \(i\) 种糖果的美味指数为 \(v_i\)。另外,如果一位游客反复地品尝同一种类的糖果,他肯定会觉得有一些腻。根据量化统计,我们得到了游客第 \(i\) 次品尝某类糖果的新奇指数 \(w_i\),如果一位游客第 \(i\) 次品尝第 \(j\) 种糖果,那么他的愉悦指数 \(H\) 将会增加对应的美味指数与新奇指数的乘积,即 \(v_j w_i\)。这位游客游览公园的愉悦指数最终将是这些乘积的和。

当然,公园中每个糖果发放点所发放的糖果种类不一定是一成不变的。有时,一些糖果点所发放的糖果种类可能会更改(也只会是 \(m\) 种中的一种),这样的目的是能够让游客们总是感受到惊喜。

糖果公园的工作人员小 A 接到了一个任务,那就是根据公园最近的数据统计出每位游客游玩公园的愉悦指数。但数学不好的小 A 一看到密密麻麻的数字就觉得头晕,作为小 A 最好的朋友,你决定帮他一把。

对于所有的数据,\(1 \leq v_i, w_i \leq 10^6\)\(1 \leq a_i, b_i \leq n\)\(1 \leq c_i \leq m\)\(w_1, w_2, \dots, w_n\) 是非递增序列,即对任意 \(1 < i \leq n\),满足 \(w_i \leq w_{i - 1}\)

题解

树上带修莫队的模板题。打个板子练练手。

时间复杂度 \(O(n^{5/3})\)

CO int N=1e5+10;
vector<int> to[N];
int fa[N],dep[N],siz[N],son[N],top[N];
int L[N],R[N],dfn,idx[2*N];

void dfs(int u){
	siz[u]=1;
	L[u]=++dfn,idx[dfn]=u;
	for(int v:to[u])if(v!=fa[u]){
		fa[v]=u,dep[v]=dep[u]+1;
		dfs(v);
		siz[u]+=siz[v];
		if(siz[v]>siz[son[u]]) son[u]=v;
	}
	R[u]=++dfn,idx[dfn]=u;
}
void divide(int u){
	if(!son[u]) return;
	top[son[u]]=top[u];
	divide(son[u]);
	for(int v:to[u])if(v!=fa[u] and v!=son[u]){
		top[v]=v;
		divide(v);
	}
}
int lca(int u,int v){
	for(;top[u]!=top[v];u=fa[top[u]])
		if(dep[top[u]]<dep[top[v]]) swap(u,v);
	return dep[u]<dep[v]?u:v;
}

int64 V[N],W[N];
int C[N],lst[N];

struct mdf {int t,u,co,cn;} P[N];
struct qry {int t,l,r,f;} Q[N];
int used[N],cnt[N];
int64 sum,ans[N];

IN void color_add(int c){
	++cnt[c],sum+=V[c]*W[cnt[c]];
}
IN void color_del(int c){
	sum-=V[c]*W[cnt[c]],--cnt[c];
}
void modify(int u,int c){
	if(used[u]) color_del(C[u]);
	C[u]=c;
	if(used[u]) color_add(C[u]);
}
void insert(int u){
	if(used[u]) color_del(C[u]),used[u]=0;
	else color_add(C[u]),used[u]=1;
}

int main(){
	int n=read<int>(),m=read<int>(),q=read<int>();
	for(int i=1;i<=m;++i) read(V[i]);
	for(int i=1;i<=n;++i) read(W[i]);
	for(int i=1;i<n;++i){
		int u=read<int>(),v=read<int>();
		to[u].push_back(v),to[v].push_back(u);
	}
	dfs(1);
	top[1]=1;
	divide(1);
	int pcnt=0,qcnt=0;
	for(int i=1;i<=n;++i) lst[i]=read(C[i]);
	for(int i=1;i<=q;++i){
		if(read<int>()==0){
			int u=read<int>(),c=read<int>();
			P[++pcnt]={i,u,lst[u],c};
			lst[u]=c;
			ans[i]=-1;
		}
		else{
			int u=read<int>(),v=read<int>();
			if(L[u]>L[v]) swap(u,v);
			int f=lca(u,v);
			if(u==f) Q[++qcnt]={i,L[u],L[v]};
			else Q[++qcnt]={i,R[u],L[v],f};
		}
	}
	CO int block=pow(dfn,(float128)2/3);
	function<int(int)> belong=[block](int x)->int{
		return (x+block-1)/block;
	};
	sort(Q+1,Q+qcnt+1,[belong](CO qry&a,CO qry&b)->bool{
		if(belong(a.l)!=belong(b.l)) return belong(a.l)<belong(b.l);
		if(belong(a.r)!=belong(b.r)) return belong(a.r)<belong(b.r);
		return (belong(a.l)+belong(a.r))&1?a.t<b.t:a.t>b.t;
	});
	int t=0,l=1,r=0;
	for(int i=1;i<=qcnt;++i){
		while(t<pcnt and Q[i].t>P[t+1].t)
			++t,modify(P[t].u,P[t].cn);
		while(t>0 and Q[i].t<P[t].t)
			modify(P[t].u,P[t].co),--t;
		while(r<Q[i].r)
			++r,insert(idx[r]);
		while(r>Q[i].r)
			insert(idx[r]),--r;
		while(l<Q[i].l)
			insert(idx[l]),++l;
		while(l>Q[i].l)
			--l,insert(idx[l]);
		if(Q[i].f) insert(Q[i].f);
		ans[Q[i].t]=sum;
		if(Q[i].f) insert(Q[i].f);
	}
	for(int i=1;i<=q;++i)if(ans[i]!=-1)
		printf("%lld\n",ans[i]);
	return 0;
}

BZOJ4129 Haruna’s Breakfast

树上带修莫队+权值分块。
有了前两天做题的经验这题已经很好打了,但是一个细节打反了让我调了好久。
过了这题可能意味着我的莫队算法已经养成了自己的风格了。

总结一下莫队算法:
普通莫队\(N^{\frac{1}{2}}\)一块,按左端点所属块与右端点排序,复杂度\(O(N^{\frac{3}{2}})\),处理直接移动区间端点。
带修莫队\(N^{\frac{2}{3}}\)一块,按左端点所属块、右端点所属块、时间排序,复杂度\(O(N^{\frac{5}{3}})\),处理先移动时间,再移动区间端点。修改操作用链表存历史版本,在当前区间内才改。
树上莫队用欧拉序,用树剖求lca,两次出现不计算,lca特殊算。

const int MAXN=1e5+7;
int n,m;
int a[MAXN];
int block,belong[MAXN];
int l[MAXN],r[MAXN];

struct Edge
{
	int to,nx;
}E[MAXN];
int head[MAXN],ecnt;

void addedge(int x,int y)
{
	E[++ecnt].to=y;
	E[ecnt].nx=head[x],head[x]=ecnt;
}

int dep[MAXN],fa[MAXN],sz[MAXN],son[MAXN],top[MAXN];
int st[MAXN],ed[MAXN],clk;
int pot[MAXN];

void dfs1(int x,int f)
{
//	cerr<<"dfsing "<<x<<endl;
	dep[x]=dep[f]+1,fa[x]=f,sz[x]=1;
	st[x]=++clk,pot[clk]=x;
	for(int i=head[x];i;i=E[i].nx)
	{
		int y=E[i].to;
		if(y==f)
			continue;
		dfs1(y,x);
		sz[x]+=sz[y];
		if(sz[y]>sz[son[x]])
			son[x]=y;
	}
	ed[x]=++clk,pot[clk]=x;
}

void dfs2(int x,int topf)
{
	top[x]=topf;
	if(!son[x])
		return;
	dfs2(son[x],topf);
	for(int i=head[x];i;i=E[i].nx)
	{
		int y=E[i].to;
		if(y==fa[x]||y==son[x])
			continue;
		dfs2(y,y);
	}
}

int lca(int x,int y)
{
	while(top[x]!=top[y])
	{
		if(dep[top[x]]<dep[top[y]])
			swap(x,y);
		x=fa[top[x]];
	}
	return dep[x]<dep[y]?x:y;
}

struct Quiz
{
	int l,r,f;
	int t,id;
	bool operator<(const Quiz&rhs)const
	{
		if(belong[l]^belong[rhs.l])
			return l<rhs.l;
		else if(belong[r]^belong[rhs.r])
			return r<rhs.r;
		else
			return t<rhs.t;
	}
}Q[MAXN];
int idxQ,ans[MAXN];
int idxC,tim[MAXN],pos[MAXN],val[MAXN],pre[MAXN],now[MAXN];


int qc,ql,qr;
int used[MAXN],cnt[MAXN],bloans[MAXN];

void change_add(int v)
{
	if(v>n)
		return;
	if(++cnt[v]==1)
		++bloans[belong[v]];
}

void change_del(int v)
{
	if(v>n)
		return;
	if(--cnt[v]==0)
		--bloans[belong[v]];
}

void change(int p)
{
	used[p]?change_del(a[p]):change_add(a[p]); // edit 1
	used[p]^=1;
}

void modify_add(int cur)
{
	if(used[pos[cur]])
	{
		change_del(a[pos[cur]]);
	}
	a[pos[cur]]=val[cur];
	if(used[pos[cur]])
	{
		change_add(a[pos[cur]]);
	}
}

void modify_del(int cur)
{
	if(used[pos[cur]])
	{
		change_del(a[pos[cur]]);
	}
	a[pos[cur]]=pre[cur];
	if(used[pos[cur]])
	{
		change_add(a[pos[cur]]);
	}
}

void modify(int t)
{
	while(qc<idxC&&tim[qc+1]<=t)
		modify_add(++qc);
	while(qc&&tim[qc]>t)
		modify_del(qc--);
}

int query()
{
	if(!cnt[0])
		return 0;
	int i;
	for(i=1;i<=(n-1)/block+1;++i)
		if(bloans[i]!=(r[i]-l[i]+1))
			break;
	int t=i;
	for(i=l[t];i<=r[t];++i)
		if(!cnt[i])
			return i;
	return n;
}

int main()
{
	read(n);read(m);
	block=pow(n,2.0/3);
	for(int i=1;i<=n;++i)
		now[i]=read(a[i]);
	for(int i=1;i<=2*n;++i)
		belong[i]=(i-1)/block+1;
	for(int i=1;i<=(n-1)/block+1;++i)
		l[i]=(i-1)*block+1,r[i]=min(i*block,n);
	for(int i=1;i<n;++i)
	{
		int x,y;
		read(x);read(y);
		addedge(x,y);
		addedge(y,x);
	}
//	cerr<<"ecnt="<<ecnt<<endl;
	dfs1(1,0);
	dfs2(1,1);
	for(int i=1;i<=m;++i)
	{
		int opt;
		read(opt);
		if(opt==0)
		{
			int u,x;
			read(u);read(x);
			tim[++idxC]=i;
			pos[idxC]=u,val[idxC]=x;
			pre[idxC]=now[u],now[u]=x;
		}
		else
		{
			int u,v;
			read(u);read(v);
			Q[++idxQ].t=i,Q[idxQ].id=idxQ;
			if(st[u]>st[v])
				swap(u,v);
			int f=lca(u,v);
			if(u==f)
				Q[idxQ].l=st[u],Q[idxQ].r=st[v];
			else
				Q[idxQ].l=ed[u],Q[idxQ].r=st[v],Q[idxQ].f=f;
		}
//		cerr<<i<<" opt="<<opt<<endl;
	}
	sort(Q+1,Q+idxQ+1);
	ql=1,qr=0,qc=0;
	for(int i=1;i<=idxQ;++i)
	{
//		cerr<<"processing "<<Q[i].id<<endl;
		modify(Q[i].t);
//		cerr<<"qc="<<qc<<endl;
		while(ql>Q[i].l)
			change(pot[--ql]);
		while(qr<Q[i].r)
			change(pot[++qr]);
		while(ql<Q[i].l)
			change(pot[ql++]);
		while(qr>Q[i].r)
			change(pot[qr--]);
		if(Q[i].f)
			change(Q[i].f);
		ans[Q[i].id]=query();
		if(Q[i].f)
			change(Q[i].f);
	}
	for(int i=1;i<=idxQ;++i)
		printf("%d\n",ans[i]);
    return 0;
}

BZOJ3585 mex

主席树

我们建立主席树,对于第\(r\)棵线段树每一个位置\(i\)存储的是数字\(i\)出现的最右端的位置。

那么对于区间询问\([l,r]\),就是找到第\(r\)棵线段树(注意不是差分)的第一个值小于\(l\)的位置(不可能大于\(r\))。

这个很容易,我们只需要维护区间最小值,询问时如果左区间的区间最小值小于\(l\)说明\(mex\)在左区间否则\(mex\)在右区间。

时间复杂度\(O(N \log V+M \log V)\),空间复杂度\(O(N \log V)\)

尽管题目\(a\)范围为\(10^9\),但我MAXD1e9过不了开1e8却能过,不知道为什么。或许题目有变动?反正我看不见这道权限题。

UPD:大于n的不用管。这句话让我受益匪浅。

我调试了一下,发现主要问题是MAXM开小了。不过昨天为什么没有RE呢?

主席树区间[0,n]就够了。果然更快。

const int MAXN=2e5+1,MAXM=4000000;//3521928.0948873623478703194294894

int root[MAXN];
struct PreSegTree
{
	int cnt;
	int minv[MAXM];
	int L[MAXM],R[MAXM];
	
	void pushup(int o)
	{
		minv[o]=min(minv[L[o]],minv[R[o]]);
	}
	
	void insert(int&now,int l,int r,int p,int v)
	{
		minv[++cnt]=minv[now];
		L[cnt]=L[now],R[cnt]=R[now];
		now=cnt;
		if(l==r)
		{
			minv[now]=v;
			return;
		}
		int mid=(l+r)>>1;
		if(p<=mid)
			insert(L[now],l,mid,p,v);
		else
			insert(R[now],mid+1,r,p,v);
		pushup(now);
	}
	
	int query(int now,int l,int r,int x)
	{
		if(l==r)
			return l;
		int mid=(l+r)>>1;
		if(minv[L[now]]>=x) // 第一个小于x的位置 
			return query(R[now],mid+1,r,x);
		else
			return query(L[now],l,mid,x);
	}
}T;

int main()
{
	int n,m;
	read(n);read(m);
	for(int i=1;i<=n;++i)
	{
		int a;
		read(a);
		root[i]=root[i-1];
		if(a>n)
			continue;
		T.insert(root[i],0,n,a,i);
	}
	while(m--)
	{
		int l,r;
		read(l);read(r);
		printf("%d\n",T.query(root[r],0,n,l));
	}
    return 0;
}

莫队

对[0,n]权值分块,大于n的权值不用管,因为它们永远不会影响答案(显然)

记录每个块的大小,每一次的移动使得该点权值数量+1或-1,记录这个移动对该点权值所在块大小的影响,也就是说如果这个块中该点权值出现次数为0(1),移动使数量+1(-1),那么块大小-1(+1)

这样的话做到了修改是O(1)的

查询时从小到大找到第一个大小不为0的块,然后在该块内部查找最小权值就行了

const int MAXN=2e5+7;
int n,m,block;
int a[MAXN],belong[MAXN],l[MAXN],r[MAXN];

struct Quiz
{
	int l,r,id;
	bool operator<(const Quiz&rhs)const
	{
		return belong[l]^belong[rhs.l]?l<rhs.l:r<rhs.r;
	}
}Q[MAXN];

int ql,qr;
int cnt[MAXN],bloans[MAXN];
int ans[MAXN];

void add(int x)
{
	if(x>n)
		return;
	if(++cnt[x]==1)
		++bloans[belong[x]];
}

void del(int x)
{
	if(x>n)
		return;
	if(--cnt[x]==0)
		--bloans[belong[x]];
}

int query()
{
	if(!cnt[0])
		return 0;
	int i;
	for(i=1;l[i];++i)
		if(bloans[i]!=r[i]-l[i]+1)
			break;
	int t=i;
	for(int i=l[t];i<=r[t];++i)
		if(!cnt[i])
			return i;
	return n;
}

int main()
{
	read(n);read(m);
	block=sqrt(n)+1e-7;
	for(int i=1;i<=n;++i)
		belong[i]=(i-1)/block+1;
	for(int i=1;(i-1)*block+1<=n;++i)
		l[i]=(i-1)*block+1,r[i]=min(i*block,n);
	for(int i=1;i<=n;++i)
		read(a[i]);
	for(int i=1;i<=m;++i)
	{
		read(Q[i].l);
		read(Q[i].r);
		Q[i].id=i;
	}
	sort(Q+1,Q+m+1);
	ql=1,qr=0;
	for(int i=1;i<=m;++i)
	{
		while(ql>Q[i].l)
			add(a[--ql]);
		while(qr<Q[i].r)
			add(a[++qr]);
		while(ql<Q[i].l)
			del(a[ql++]);
		while(qr>Q[i].r)
			del(a[qr--]);
		ans[Q[i].id]=query();
	}
	for(int i=1;i<=m;++i)
		printf("%d\n",ans[i]);
    return 0;
}

Hnoi2016 序列

首先这道题的难点在于拓展当前统计的答案区间.也就是说:在已经计算出来了区间\([l,r]\)情况下,如何使其拓展到\([l,r+1]\)

首先我们知道枚举区间再计算区间的价值是不明智的。我们应该考虑枚举所有的元素,考虑其对区间的贡献.

在上面的拓展中,我们设\(left_i\)表示\(a_i\)前面第一个比它小的值的下标.

所以我们知道在拓展时新增的\((r+1)−l+1\)个区间中左端点在\([left_{r+1}+1,r+1]\)内的区间的价值一定都是\(a_{r+1}\)

所以现在我们的问题就是如何处理\([l,left_{r+1}]\)这段区间的贡献了.

我们设:\(f(i,j)\)表示处理区间\([i,j]\)所得到的贡献.

那么我们有:

\(f(i,j)=f(i,left_j)+(j−left_j)∗a_j\)

我们发现实际上\(i\)只是限定了一个左端点而已.

所以我们查询区间\([l,r]\)时能够简单地取出\(f(l,r)\)

所以我们将\(f\)的第一个维度消去,即:

\(f(i)=f(left_i)+(i−left_i)∗a_i\)

这样我们在查询区间\([l,r]\)\(l\)满足是某前缀区间最小值的位置的下一个位置)时取出\((f(r) - f(l-1))\)即可.

注意到对于\(f(left_i,j)\)这样的贡献,无论\(j\)如何变化,计算式是永远不变的,所以可以预处理(使用单调栈)。有什么用呢?可以优化普通做法。

  • 普通做法:对于一段区间\([l,r]\),假设在右侧加入一个元素\(r+1\),设\([l,r+1]\)中的最小值所在位置\(x\),则一部分增加的代价为\((x-l+1)*a[x]\),再递归求\([x+1,r+1]\)的代价,最坏复杂度为\(O(N)\)

  • 优化做法:将递归过程用\(f\)加速,复杂度\(O(1)\),于是便可以用莫队处理。

删除元素可以用已实现的增加元素操作实现,方法是把带删除的元素当做是新增的,新增的增加量即为删除的减少量。

时间复杂度\(O(N \sqrt{Q})\)

UPD:其实这个做法是观察到拓展[l,r+1]的时候大部分的更新与拓展[1,r+1]一模一样,只有[l,r+1]最小值左侧的区间更新数量不完全。

const int MAXN=1e5+7;
int n,q;
int a[MAXN];
int logar[MAXN],minv[MAXN][18],minid[MAXN][18];

inline void pre()
{
	logar[1]=0;
	for(rg int i=1;i<=n;++i)
		logar[i]=logar[i-1]+(i==1<<(logar[i-1]+1));
	for(rg int i=n;i>=1;--i)
	{
		minv[i][0]=a[i];
		minid[i][0]=i;
		for(rg int j=1;i+(1<<j)-1<=n;++j)
		{
			minv[i][j]=minv[i][j-1],minid[i][j]=minid[i][j-1];
			if(minv[i+(1<<(j-1))][j-1]<minv[i][j])
				minv[i][j]=minv[i+(1<<(j-1))][j-1],minid[i][j]=minid[i+(1<<(j-1))][j-1];
		}
	}
}

inline int query(int l,int r)
{
	int k=logar[r-l+1];
	if(minv[l][k]<minv[r-(1<<k)+1][k])
		return minid[l][k];
	else
		return minid[r-(1<<k)+1][k];
}

ll fl[MAXN],fr[MAXN];

inline void dp(ll*f)
{
	stack<int>S;
	S.push(0);
	for(rg int i=1;i<=n;++i)
	{
		while(a[S.top()]>a[i])
			S.pop();
		f[i]=ll(i-S.top())*a[i]+f[S.top()];
		S.push(i);
	}
}

int bl[MAXN];
struct Quiz
{
	int l,r;
	int id;
	bool operator<(const Quiz&x)const
	{
		if(bl[l]^bl[x.l])
			return l<x.l;
		else
			return r<x.r;
	}
}Q[MAXN];
int ql,qr;
ll res,ans[MAXN];

inline ll upd_L(int l,int r)
{
	int p=query(l-1,r);
	return ll(r-p+1)*a[p]+fr[l-1]-fr[p];
}

inline ll upd_R(int l,int r)
{
	int p=query(l,r+1);
	return ll(p-l+1)*a[p]+fl[r+1]-fl[p];
}

int main()
{
	read(n);read(q);
	a[0]=-INF;
	for(rg int i=1;i<=n;++i)
		read(a[i]);
	pre();
	dp(fl);
	reverse(a+1,a+n+1);
	dp(fr);
	reverse(fr+1,fr+n+1);
	reverse(a+1,a+n+1);
	int B=sqrt(n);
	for(rg int i=1;i<=n;++i)
		bl[i]=(i-1)/B+1;
	for(rg int i=1;i<=q;++i)
	{
		read(Q[i].l);read(Q[i].r);
		Q[i].id=i;
	}
	sort(Q+1,Q+q+1);
	a[0]=0;
	ql=1,qr=0,res=0;
	for(rg int i=1;i<=q;++i)
	{
		while(ql>Q[i].l)
			res+=upd_L(ql--,qr);
		while(qr<Q[i].r)
			res+=upd_R(ql,qr++);
		while(ql<Q[i].l)
			res-=upd_L(++ql,qr);
		while(qr>Q[i].r)
			res-=upd_R(ql,--qr);
		ans[Q[i].id]=res;
	}
	for(rg int i=1;i<=q;++i)
		printf("%lld\n",ans[i]);
    return 0;
}

BZOJ2120 数颜色

https://www.cnblogs.com/RabbitHu/p/MoDuiTutorial.html

然而实际上那篇博客里的代码是错的,不知道为什么能AC……

如果有连续修改某个点的权值,那么pre数组就不能处理。要改很简单,其实就多开一个辅助数组now,用链表的结构存储修改就行了。

const int MAXN=1e4+7,MAXC=1e6+7;

int n,m,B;
int ql=1,qr=0,cur,res;
int ans[MAXN],a[MAXN],cnt[MAXC];

int idxC,idxQ,tim[MAXN],pos[MAXN],val[MAXN],pre[MAXN],now[MAXN];

inline int bl(int x)
{
	return (x-1)/B+1;
}

struct query
{
	int id,tim,l,r;
	bool operator<(const query&x)const
	{
		if(bl(l)^bl(x.l))
			return l<x.l;
		else if(bl(r)^bl(x.r))
			return r<x.r;
		else
			return tim<x.tim;
	}
}q[MAXN];

inline void change_add(int cur)
{
	if(ql<=pos[cur]&&pos[cur]<=qr)
	{
		--cnt[a[pos[cur]]];
		if(!cnt[a[pos[cur]]])
			--res;
	}
	a[pos[cur]]=val[cur];
	if(ql<=pos[cur]&&pos[cur]<=qr)
	{
		if(!cnt[a[pos[cur]]])
			++res;
		++cnt[a[pos[cur]]];
	}
}

inline void change_del(int cur)
{
	if(ql<=pos[cur]&&pos[cur]<=qr)
	{
		--cnt[a[pos[cur]]];
		if(!cnt[a[pos[cur]]])
			--res;
	}
	a[pos[cur]]=pre[cur];
	if(ql<=pos[cur]&&pos[cur]<=qr)
	{
		if(!cnt[a[pos[cur]]])
			++res;
		++cnt[a[pos[cur]]];
	}
}

inline void change(int t)
{
	while(cur<idxC&&tim[cur+1]<=t)
		change_add(++cur);
	while(cur&&tim[cur]>t)
		change_del(cur--);
}

inline void add(int p)
{
	if(!cnt[a[p]])
		++res;
	++cnt[a[p]];
}

inline void del(int p)
{
	--cnt[a[p]];
	if(!cnt[a[p]])
		--res;
}

int main()
{
	read(n);read(m);
	B=pow(n,2.0/3);
	for(rg int i=1;i<=n;++i)
		now[i]=read(a[i]);
	for(rg int i=1;i<=m;++i)
	{
		char opt[2];
		scanf("%s",opt);
		if(opt[0]=='Q')
		{
			++idxQ;
			q[idxQ].id=idxQ,q[idxQ].tim=i;
			read(q[idxQ].l);read(q[idxQ].r);
		}
		else if(opt[0]=='R')
		{
			tim[++idxC]=i;
			read(pos[idxC]);
			read(val[idxC]);
			pre[idxC]=now[pos[idxC]];
			now[pos[idxC]]=val[idxC];
		}
	}
	sort(q+1,q+idxQ+1);
	for(rg int i=1;i<=idxQ;++i)
	{
		change(q[i].tim);
		while(ql>q[i].l)
			add(--ql);
		while(qr<q[i].r)
			add(++qr);
		while(ql<q[i].l)
			del(ql++);
		while(qr>q[i].r)
			del(qr--);
		ans[q[i].id]=res;
	}
	for(rg int i=1;i<=idxQ;++i)
		printf("%d\n",ans[i]);
    return 0;
}

posted on 2020-02-22 17:38  autoint  阅读(223)  评论(0编辑  收藏  举报

导航