【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;
}