题目链接:BZOJ2120
题目大意:
维护一个序列(长度小于等于10000),支持两种操作:查询和修改。
查询:查询区间[L,R]中的不同数字的个数。修改:将第i个元素修改为j。
题解:
本题是带修改的莫队算法。
因为莫队算法是通过改变询问的顺序来降低整体的时间复杂度,必须保证离线,而加入修改操作相当于要求在线,故普通的莫队无法解决此题。
我们考虑加入修改操作后如何尽量保证算法的复杂度。同普通的莫队算法,我们将所有操作先记录下来(离线)。
注意查询操作只有在查询之前的所有修改操作完成之后才能保证该查询的正确性。所以我们只要记录了之前有多少个修改操作,然后在执行到当前查询时,把多进行的操作还原或把少进行的操作补齐就可以保证正确性。但这样做会被卡掉,原因就是我们的修改操作可能很多(想一下每次莫队计算答案时至多都要1000次修改,最多需要9000*1000=9000000次修改)。
此时,为了同时保证莫队算法的复杂度,尝试更改莫队算法的排序条件。我们采取的策略是按l所在的块,r所在的块,该询问操作前的修改次数,为第一、二、三关键字排序。
这样可以保证时间复杂度较优,同时注意,对于n和m同级的题目,块的大小为时复杂度为,可以证明,此处略去。而对于本题来讲,修改次数少,所以块的大小为更快。
下面是我的代码。
#include<cstdio>
#include<cmath>
#include<algorithm>
using namespace std;
const int maxn=10001;
int n,m,block,quescnt,chancnt,L,R,head,ans;
int a[maxn],last[maxn],A[maxn],cnt[1000001];
char ch[5];
struct Ask{//查询。
int l,r,lb,rb,tim,id;
//lb表示左端所在的块,rb同理,tim记录查询前有多少次修改。
}question[maxn];
struct Modify{//修改操作。
int x,y,last;
}change[maxn];
inline int getblock(int num){
return (num-1)/block+1;
}
bool cmp(Ask q,Ask qq){
if (q.lb==qq.lb){
if (q.rb==qq.rb) return q.tim<qq.tim;
return q.rb<qq.rb;
}
return q.lb<qq.lb;
}
void Change(int x,int col){
if (L<=x && x<=R){//要修改的位置在当前区间。
cnt[a[x]]--;
if (cnt[a[x]]==0) ans--;
a[x]=col;
if (cnt[a[x]]==0) ans++;
cnt[a[x]]++;
}
else a[x]=col;//要修改的位置不在当前区间。
}
void Update(int x,int d){
int temp=cnt[a[x]];
cnt[a[x]]+=d;
if (temp==0 && cnt[a[x]]==1) ans++;
else if (temp==1 && cnt[a[x]]==0) ans--;
}
void Mo(){
L=1,R=0,head=0;//L和R是莫队算法的两边,head是当前询问指针。
for (int i=1;i<=quescnt;i++){
while (head>question[i].tim){//还原修改操作。
Change(change[head].x,change[head].last);
head--;
}
while (head<question[i].tim){//补齐修改操作。
head++;
Change(change[head].x,change[head].y);
}
//莫队算法。
while (R<question[i].r) R++,Update(R,1);
while (L>question[i].l) L--,Update(L,1);
while (R>question[i].r) Update(R,-1),R--;
while (L<question[i].l) Update(L,-1),L++;
A[question[i].id]=ans;
}
}
int main(){
// freopen("BZOJ2120.in","r",stdin);
// freopen("BZOJ2120.out","w",stdout);
scanf("%d%d",&n,&m);
for (int i=1;i<=n;i++) scanf("%d",&a[i]),last[i]=a[i];
block=sqrt(n);
for (int i=1;i<=m;i++){
scanf("%s",ch);
if (ch[0]=='R'){
chancnt++;
scanf("%d%d",&change[chancnt].x,&change[chancnt].y);
//此处记录last数组以便还原修改。
change[chancnt].last=last[change[chancnt].x];
last[change[chancnt].x]=change[chancnt].y;
}
else {
quescnt++;
scanf("%d%d",&question[quescnt].l,&question[quescnt].r);
//记录提问的相关信息。
question[quescnt].id=quescnt;
question[quescnt].lb=getblock(question[quescnt].l);
question[quescnt].rb=getblock(question[quescnt].r);
question[quescnt].tim=chancnt;
}
}
sort(question+1,question+quescnt+1,cmp);
Mo();
for (int i=1;i<=quescnt;i++) printf("%d\n",A[i]);
return 0;
}