BZOJ原题地址
洛谷原题地址
思路
首先,这道题就是带修改的HH的项链,而后者是一道非常经典的入门莫队题。。。
所以我一开始就考虑写一个带修改的莫队尝试水过这道题。
具体地,对于每个询问,记录它之前的最后一次修改的位置,于是就在移动指针的之后,处理一下单点修改的问题。
有一个小技巧就是单点修改是把目标位置的颜色和修改操作的颜色互换,这样在你来回修改的时候能保证结果的正确性,并且时间复杂度很优。
于是我们就愉快地在BZOJ上A掉了这道题。
然而。。。
洛谷的数据是“Proudly Made With CYaRon”,在说明里面就提示了“轻微卡常数”。
多轻微呢。。。
带修改的莫队在洛谷上只有40分。
所以我滚去学习了一下如何使用分块解决这个问题。
具体地,这种询问区间出现过的元素个数的问题有个套路,就是对于每个位置可以维护这个数前一个出现的位置。假设位置$i$是$a_i$,$a_i$前一次出现在$x$,我们设$p_i=x$,那么询问就是在这个区间$[l,r]$中有多少个$p_i$<$l$。
代码实现
其实这份代码是萎的。。。
分块依然只有40分,hzwer的代码也TLE了。。。
所以洛谷的数据对我无解(
#include<iostream>
#include<cstdio>
#include<cmath>
#include<algorithm>
#include<cstring>
#define N 10001
#define M 1000001
using namespace std;
int n,q,m,block;
int c[N],pos[N],pre[N],b[N],last[M];
int find(int x,int v)
{
int l=(x-1)*block+1,r=min(x*block,n);
int first=l;
while(l<=r)
{
int mid=(l+r)>>1;
if(pre[mid]<v)l=mid+1;
else r=mid-1;
}
return l-first;
}
void reset(int x)
{
int l=(x-1)*block+1,r=min(x*block,n);
for(int i=l;i<=r;i++)pre[i]=b[i];
sort(pre+l,pre+r+1);
}
void build()
{
for(int i=1;i<=n;i++)
{
b[i]=last[c[i]];
last[c[i]]=i;
pos[i]=(i-1)/block+1;
}
for(int i=1;i<=m;i++)reset(i);
}
int ask(int l,int r)
{
int ans=0;
if(pos[l]==pos[r])
{
for(int i=l;i<=r;i++)if(b[i]<l)ans++;
}
else
{
for(int i=l;i<=block*pos[l];i++)if(b[i]<l)ans++;
for(int i=block*(pos[r]-1)+1;i<=r;i++)if(b[i]<l)ans++;
}
for(int i=pos[l]+1;i<pos[r];i++)
ans+=find(i,l);
return ans;
}
void change(int x,int v)
{
for(int i=1;i<=n;i++)last[c[i]]=0;
c[x]=v;
for(int i=1;i<=n;i++)
{
int t=b[i];
b[i]=last[c[i]];
if(t!=b[i])reset(pos[i]);
last[c[i]]=i;
}
}
int main()
{
scanf("%d%d",&n,&q);
for(int i=1;i<=n;i++)scanf("%d",&c[i]);
block=int(sqrt(n)+log(2*n)/log(2));
if(n%block)m=n/block+1;
else m=n/block;
build();
char ch[5];int x,y;
for(int i=1;i<=q;i++)
{
scanf("%s%d%d",ch,&x,&y);
if(ch[0]=='Q')printf("%d\n",ask(x,y));
else change(x,y);
}
return 0;
}