BZOJ 2038 小Z的袜子
这是一道很经典的题目了,解法当然是莫队算法.
推导过程我有空再写,那么先写一写结论.
对于某个区间,小Z要求的概率为这个区间内所有颜色个数的平方和减去区间长度的差除以区间长度的平方减去区间长度的差
记这些颜色的数目为$c_1,c_2,c_3,\dots,c_n$,那么答案就是\[\frac{\left(\sum\limits_{i=1}^{n}c_i^2\right)-(R-L+1)}{(R-L+1)\cdot (R-L)}\]
这个结论很经典,非常经典.这个结论无法相加,也就是无法合并.但是它可以拓展,或者缩小自己的范围,而增加或减少一只袜子的信息都只要$O(1)$的时间.那么我们用某种方式给输入数据排个序,按顺序处理这些输入,显然,处理顺序不同所要的时间也就不同.
首先思考如何做到$O\left(n^2\right)$.注意到我们只需要让其中的一个参数(比如$R$)单调递增就可以做到了.(如何证明自己想一想)那么我们将另一个参数分$\sqrt{n}$块,那么块内的所有询问总复杂度就是$O(n)$了,因为总共有$O(\sqrt{n})$块,那么总复杂度就是$O(n\sqrt{n})$了.
#include <cstdio> #include <algorithm> #include <cmath> typedef int INT; #define int unsigned int int gcd(int a,int b){ if(b) return gcd(b,a%b); return a; } inline int sqr(int a){return a*a;} struct upderr{ int l,r,colornum[60000],p; inline void updadd(int c,int lc,int rc){ l-=lc,r+=rc; p=p-sqr(colornum[c])+sqr(colornum[c]+1); ++colornum[c]; } inline void updsub(int c,int lc,int rc){ l+=lc,r-=rc; p=p-sqr(colornum[c])+sqr(colornum[c]-1); --colornum[c]; } inline void fetch(int& up,int& down){ down=r-l+1; up=p-down; down=down*(down-1); int pp=gcd(up,down); up/=pp,down/=pp; } } upder; int pos[100000],i,sl,n,m,color[100000],ans[100000][2]; struct query{ int l,r,id; void performQuery(){ if(l==r) ans[id][0]=0,ans[id][1]=1; else{ while(upder.l>l){ upder.updadd(color[upder.l-1],1,0); } while(upder.r<r){ upder.updadd(color[upder.r+1],0,1); } while(upder.l<l){ upder.updsub(color[upder.l],1,0); } while(upder.r>r){ upder.updsub(color[upder.r],0,1); } upder.fetch(ans[id][0],ans[id][1]); } } } ies[100000]; bool cmp(query a,query b){ if(pos[a.l]<pos[b.l]) return true; if(pos[a.l]>pos[b.l]) return false; return a.r<b.r; } INT main(){ freopen("littleZ.in","r",stdin); freopen("littleZ.out","w",stdout); scanf("%d%d",&n,&m); sl=round(sqrt(n)); for(i=1;i<=n;++i){ pos[i]=(i-1)/sl; } for(i=1;i<=n;++i) scanf("%d",color+i); for(i=0;i<m;++i) scanf("%d%d",&ies[i].l,&ies[i].r),ies[i].id=i; std::sort(ies,ies+m,cmp); upder.l=upder.r=1; upder.colornum[color[upder.l]]=1; upder.p=1; for(i=0;i<m;++i) ies[i].performQuery(); for(i=0;i<m;++i) printf("%d/%d\n",ans[i][0],ans[i][1]); return 0; }
注意这题要用unsigned int,切记.