Link:
Solution:
很久以前这道题我是用$BIT$过的
思路非常简单:离线将询问排序,记录每个点同一颜色的后继节点
随着询问左边界的递增,将处于两次左边界间的节点从$BIT$中删去,而加入其后继节点的位置
接下来统计处于$[l,r]$间的数的个数就好了
同时这道题也是莫队算法的经典适用情况:离线的区间问题
先安利一篇博客:传送门 果然女队的文章写得就是清楚啊
感觉莫队算法实际上就是离线问题中将询问按一定方式排序后的优雅暴力
我们将$n$个点分为$sqrt(n)$块,所有询问按以下规则排序:
1、左边界所在块的编号为第一关键字
2、右边界的位置为第二关键字
按这样的顺序暴力移动左右指针处理每个询问就能将复杂度控制在$O(n*log(n))$了!
简易证明:
1、对于左指针,每次询问最多移动块长度$sqrt(n)$,因此总复杂度为$O(n*sqrt(n))$
2、对于右指针,当左指针在同一块时有序,最多移动$n$,而只有$sqrt(n)$块,因此总复杂度也为$O(n*sqrt(n))$
通过构造让左右指针“互相迁就”的方式使复杂度符合了要求
可能是现在看到最优雅的算法之一?看来我还是适合暴力啊
Code:
#include <bits/stdc++.h> using namespace std; typedef pair<int,int> P; typedef pair<P,int> PP; #define F first #define S second const int MAXN=1000010; int dat[MAXN],bit[MAXN],res[MAXN],nxt[MAXN],cur[MAXN],n,c,m; PP op[MAXN]; inline int read() { char ch;int num,f=0; while(!isdigit(ch=getchar())) f|=(ch=='-'); num=ch-'0'; while(isdigit(ch=getchar())) num=num*10+ch-'0'; return f?-num:num; } inline void write(long long x) { if(x<0) putchar('-'),x=-x; if(x>9) write(x/10); putchar(x%10+'0'); } void update(int pos,int x) { while(pos<=n) { bit[pos]+=x; pos+=pos&(-pos); } } int cal(int pos) { int ret=0; while(pos) { ret+=bit[pos]; pos-=pos&(-pos); } return ret; } int main() { n=read(); for(int i=1;i<=n;i++) dat[i]=read(); m=read(); for(int i=1;i<=m;i++) op[i].F.F=read(),op[i].F.S=read(),op[i].S=i; fill(nxt,nxt+MAXN,MAXN-2); for(int i=1;i<=n;i++) { if(!cur[dat[i]]) update(i,1); nxt[cur[dat[i]]]=i,cur[dat[i]]=i; } op[0].F.F=1; sort(op+1,op+m+1); for(int i=1;i<=m;i++) { for(int j=op[i-1].F.F;j<op[i].F.F;j++) update(j,-1),update(nxt[j],1); res[op[i].S]=cal(op[i].F.S)-cal(op[i].F.F-1); } for(int i=1;i<=m;i++) write(res[i]),putchar('\n'); return 0; }