题意:
给出一个长度为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)查询logn,算法总复杂度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; }
原文地址:http://blog.csdn.net/ww140142/article/details/47315861