询问序列区间中权值在给定区间内权值种类数。
我们可以莫队算法,然后用线段树来维护,这个思路十分显然。
这样in和out的复杂度均为log n,而query的复杂度为log n。
同样使用莫队,让我们思考我们需要兹瓷的功能。
1、插入一个数(in)
2、删除一个数(out)
3、询问区间权值种类(query)
上面用线段树来兹瓷这三个操作,由于in和out时间复杂度较大所以总的时间复杂度较大。我们可以考虑减小in和out的复杂度,当然代价是增大query的复杂度。
我们可以分块,于是in和out就变成o(1)的了!相应的query复杂度是根号。
#include<cstdio>
#include<algorithm>
#include<cmath>
#define fo(i,a,b) for(i=a;i<=b;i++)
using namespace std;
const int maxn=100000+10,maxm=1000000+10;
struct dong{
int l,r,a,b,id;
};
int belong[maxn],a[maxn],ans[maxm],cnt[maxn],num[1000];
dong ask[maxm];
int i,j,k,l,r,t,n,m,c;
bool cmp(dong a,dong b){
if (belong[a.l]<belong[b.l]) return 1;
else if (belong[a.l]==belong[b.l]&&a.r<b.r) return 1;
else return 0;
}
void in(int x){
cnt[x]++;
if (cnt[x]==1) num[belong[x]]++;
}
void out(int x){
cnt[x]--;
if (cnt[x]==0) num[belong[x]]--;
}
int query(int j,int k){
int i,t=0;
int l=belong[j],r=belong[k];
if (r-l<=1){
fo(i,j,k)
if (cnt[i]>0) t++;
return t;
}
fo(i,l+1,r-1) t+=num[i];
fo(i,j,l*c)
if (cnt[i]>0) t++;
fo(i,(r-1)*c+1,k)
if (cnt[i]>0) t++;
return t;
}
int read(){
int x=0;
char ch=getchar();
while (ch<'0'||ch>'9') ch=getchar();
while (ch>='0'&&ch<='9'){
x=x*10+ch-'0';
ch=getchar();
}
return x;
}
int main(){
n=read();m=read();
c=floor(sqrt(n));
fo(i,1,n) belong[i]=(i-1)/c+1;
fo(i,1,n) a[i]=read();
fo(i,1,m){
ask[i].l=read();ask[i].r=read();ask[i].a=read();ask[i].b=read();
ask[i].id=i;
}
sort(ask+1,ask+m+1,cmp);
l=r=1;
in(a[1]);
fo(i,1,m){
while (l<ask[i].l) out(a[l++]);
while (l>ask[i].l) in(a[--l]);
while (r<ask[i].r) in(a[++r]);
while (r>ask[i].r) out(a[r--]);
ans[ask[i].id]=query(ask[i].a,ask[i].b);
}
fo(i,1,m) printf("%d\n",ans[i]);
}
具体看2014国家队论文集里,徐寅展的论文。