题意:
给出一个长度为n的数列,每个数字在[1,n]内;
m次询问,查询[l,r]区间中值在[a,b]中的数字种类数;
n<=100000,m<=1000000;
内存限制为28M
题解:
出题人实在太丧病系列;
莫队算法+树状数组这个比较显然吧;
码了一发交上去MLE了,砍了砍内存的常数,还是MLE;
然后发现询问里不能记录左端点所在块。。。在cmp里现求是吗。。。
改完T了!加完读入优化还是T!
没办法,去找了找正解;
正解是把树状数组求和改成了分块求和;
看起来比较慢但是时间复杂度是降低的;
原来的树状数组每次插入logn查询logn,算法总复杂度O(n√nlogn);
而改成分块之后每次插入O(1)查询√n,算法总复杂度O((n+m)n);
这题就这么卡过了,卡常数新技巧get√?
反正刷完这题我是第一次见到了bzoj的提交限制数;
代码:
#include<math.h>
#include<stdio.h>
#include<string.h>
#include<algorithm>
#define N 100001
using namespace std;
inline int read()
{
int x=0,f=1;char ch=getchar();
while(ch>'9'||ch<'0'){if(ch=='-')f=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
return x*f;
}
int n,bk;
struct node
{
int l,r,a,b,no;
}Q[N*10];
int a[N],s[N],block[1000],ans[N*10];
bool vis[N];
inline int lowbit(int x)
{
return x&(-x);
}
inline bool cmp(node a,node b)
{
if(a.l/bk==b.l/bk)
return a.r<b.r;
return a.l/bk<b.l/bk;
}
inline void add(int x)
{
vis[x]=!vis[x];
block[x/bk]+=vis[x]?1:-1;
}
inline int query(int x,int y)
{
int ret=0,bx=x/bk,by=y/bk;
if(bx==by)
{
for(int i=x;i<=y;i++)
ret+=vis[i];
return ret;
}
for(int i=bx+1;i<by;i++)
ret+=block[i];
for(int i=x;i<(bx+1)*bk;i++)
ret+=vis[i];
for(int i=by*bk;i<=y;i++)
ret+=vis[i];
return ret;
}
inline void update(int x,int op)
{
if(!s[x]) add(x);
s[x]+=op;
if(!s[x]) add(x);
}
int main()
{
int m,i,j,k,l,r;
scanf("%d%d",&n,&m);
bk=sqrt(n);
for(i=1;i<=n;i++)
a[i]=read();
for(i=1;i<=m;i++)
{
Q[i].l=read(),Q[i].r=read(),Q[i].a=read(),Q[i].b=read();
Q[i].no=i;
}
sort(Q+1,Q+m+1,cmp);
l=1,r=1,s[a[1]]=1,add(a[1]);
for(i=1;i<=m;i++)
{
while(l<Q[i].l) update(a[l++],-1);
while(l>Q[i].l) update(a[--l],1);
while(r<Q[i].r) update(a[++r],1);
while(r>Q[i].r) update(a[r--],-1);
ans[Q[i].no]=query(Q[i].a,Q[i].b);
}
for(i=1;i<=m;i++)
printf("%d\n",ans[i]);
return 0;
}