Luogu P1903 [国家集训队] 数颜色 / 维护队列
Luogu P1903(带修莫队)
莫队算法是解决已知 \([l,r]\) 答案后求 \([l-1,r]\) 或是 \([l,r+1]\) 答案的一类算法,只要可以在 \(\text O(1)\) 的时间内转移,都可以使用莫队算法。
本题除了单纯的查询 \([l,r]\) 的答案这一操作,还有修改这一操作,显然普通莫队无法胜任这个问题,这时候就要使用带修改的莫队算法。
与普通的莫队算法相比,带修莫队多了一个时间轴 \(t\) ,而这一多出的时间轴使得这种莫队算法支持修改操作。
具体实现方式与普通莫队相同,只不过在状态转移的时候要根据时间进行修改操作,如果前者 \(t_1\) 大于后者 \(t_2\) ,那么就要将 \([t1,t2]\) 之间的修改操作撤销,反之则是进行修改。
莫队算法的时间复杂度受块长影响很大,本题中块长取 \(n^{\frac{2}{3}}\) 可以使得整体时间复杂度为最优的 \(\text O(n^{\frac{5}{3}})\)
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<limits.h>
#include<cmath>
#define mem(a,b) memset(a,b,sizeof(a));
using namespace std;
template<typename T> void read(T &k)
{
k=0;
T flag=1;char b=getchar();
while (b<'0' || b>'9') {flag=(b=='-')?-1:1;b=getchar();}
while (b>='0' && b<='9') {k=(k<<3)+(k<<1)+(b^48);b=getchar();}
k*=flag;
}
const int _SIZE=133333,_MAXN=1e6;
int n,m;
int a[_SIZE+5],cnt[_MAXN+5],ans[_SIZE+5];
int len,sum;
struct QUERY{
int l,r,t,id;
}que[_SIZE+5],mod[_SIZE+5];
void add(int x)
{
sum+=!cnt[x]++;
}
void del(int x)
{
sum-=!--cnt[x];
}
void update(int x,int t)
{
if (que[x].l<=mod[t].l && mod[t].l<=que[x].r)
{
del(a[mod[t].l]);
add(mod[t].r);
}
swap(a[mod[t].l],mod[t].r);
}
bool cmp(QUERY x,QUERY y)
{
if (x.l/len!=y.l/len) return x.l<y.l;
if (x.r/len!=y.r/len) return x.r<y.r;
return x.t<y.t;
}
int cntq,cntm;
int main()
{
read(n);read(m);
len=pow(n,0.66);
for (int i=1;i<=n;i++) read(a[i]);
for (int i=1;i<=m;i++)
{
char com[5];scanf("%s",com);
int _t1,_t2;read(_t1),read(_t2);
if (com[0]=='Q')
{
cntq++,
que[cntq].id=cntq,que[cntq].l=_t1,
que[cntq].r=_t2,que[cntq].t=cntm;
}
else
{
cntm++,
mod[cntm].l=_t1,mod[cntm].r=_t2;
}
}
sort(que+1,que+cntq+1,cmp);
int lcur=1,rcur=0,tcur=0;
for (int i=1;i<=cntq;i++)
{
while (lcur>que[i].l) add(a[--lcur]);
while (lcur<que[i].l) del(a[lcur++]);
while (rcur>que[i].r) del(a[rcur--]);
while (rcur<que[i].r) add(a[++rcur]);
while (tcur<que[i].t) update(i,++tcur);
while (tcur>que[i].t) update(i,tcur--);
ans[que[i].id]=sum;
}
for (int i=1;i<=cntq;i++) printf("%d\n",ans[i]);
return 0;
}