【BZOJ】【2038】小Z的袜子

填个坑吧,学习了莫队算法。我也忘记是看的哪位大牛的博客&代码学习的了T_T,如果您发现了的话请私信我,我会注明学自您的代码。

另外感谢@PoPoQQQ大神

 

好,进入正文,莫队算法,也算是一种暴力吧,从某种意义上来说……是一种对暴力的强大优化= =

对于区间的莫队是基于【分块】的思想,这一点有些类似求离散对数的【大步小步算法】。

首先我们来考虑一个赤果果的暴力算法:对于每个询问,从L到R枚举一遍,统计每种袜子出现的次数,然后用组合数学的方法算出抽到两只相同袜子的方案数,和所有抽取袜子的总方案数。(至于求GCD然后约分什么的应该都懂的)

 

实际上,我们在进行暴力枚举的时候,有很多区间在不同的询问中计算了多次,而实际上这些已经进行过的计算完全是可以尽可能地进行再次利用的!两个相差不大的询问的结果相似度是很高的,我们完全可以在进行完一个[l,r]的计算后,进行少许更改,快速转移到[l',r']。

这就可以通过对询问顺序进行适当调整来实现:没错!排序!

 

那么问题来了:要按照什么样的顺序来排序呢?莫队发明的莫队算法提出了一种很科学的解决方案:分块。

把长度为N的序列分为sqrt(N)块,每块大小为sqrt(N),然后按 l 所在块的先后顺序排,l 在同一块内的按r的递增顺序排。(为什么这样分?根据基本不等式等等等等可以推出这样复杂度最低啊!当然如果带修改还会有别的分法)

排好序了,接下来就好办啦~先算出来这个块第一个询问的答案,然后不断执行-1+1调整区间。详细内容看代码注释吧~

 1 //BZOJ 2038
 2 #include<cmath>
 3 #include<cstdio>
 4 #include<cstring>
 5 #include<cstdlib>
 6 #include<iostream>
 7 #include<algorithm>
 8 #define rep(i,n) for(int i=0;i<n;++i)
 9 #define F(i,j,n) for(int i=j;i<=n;++i)
10 #define D(i,j,n) for(int i=j;i>=n;--i)
11 using namespace std;
12 const int N=50010;
13 typedef long long LL;
14 
15 int n,sqr;
16 struct qes{
17     LL t,l,r;
18     bool operator < (const qes &a) const{
19         return (l/sqr)<(a.l/sqr) || (l/sqr)==(a.l/sqr) && r<a.r;
20     }//第一个条件是按 l 所在块排,第二个条件是按 r 单调增 
21 }q[N];
22 
23 LL gcd(LL a,LL b){
24     if (!b) return a;
25     else return gcd(b,a%b);
26 }
27 
28 LL Ans[N][2];
29 
30 int m,c[N],cnt[N];
31 
32 int main(){
33     #ifndef ONLINE_JUDGE
34     freopen("file.in","r",stdin);
35     #endif
36     scanf("%d%d",&n,&m);
37     sqr=int(sqrt(n));
38     F(i,1,n) scanf("%d",&c[i]);
39     F(i,1,m){
40         scanf("%d%d",&q[i].l,&q[i].r);
41         q[i].t=i;
42     }
43     sort(q+1,q+m+1);
44     
45     LL rec;
46     int l,r;
47     F(i,1,m){
48         if (i==1 || q[i].l/sqr != q[i-1].l/sqr){
49         //如果是一个新的块就重新计算(将这个块第一个询问结果作为当前块所有询问的基底)
50             F(j,0,n) cnt[j]=0;
51             l=q[i].l; r=q[i].r;
52             F(j,l,r) ++cnt[c[j]];//本询问区间内计数统计 
53             rec=0;//rec表示选出相同袜子的方案数
54             F(j,0,n) rec+=(LL)(cnt[j])*(LL)(cnt[j]-1);
55         }
56         
57         
58         //这三个for循环是 -1 +1 更新本节点的值
59         for(;r<q[i].r;++r){
60         //对于l在同一个块中的q,是按r单调增的顺序排序了的……
61             int s=c[r+1];
62             rec-=(LL)(cnt[s])*(LL)(cnt[s]-1);
63             ++cnt[s];
64             rec+=(LL)(cnt[s])*(LL)(cnt[s]-1);
65             //重新计算当前区间的答案
66 //比如原来是[2,3,3]现在变成了[2,3,3,3]那么就要减去颜色为3的袜子对答案的贡献 
67 //再加上数量增加了1以后  它对答案的贡献 
68         }
69         for(;l<q[i].l;++l){
70         //但是在同一块中,l并不一定是单调增的,所以会有向左右两个方向的调整 
71         //这是向右 
72             int s=c[l];
73             rec-=(LL)(cnt[s])*(LL)(cnt[s]-1);
74             --cnt[s];
75             rec+=(LL)(cnt[s])*(LL)(cnt[s]-1);
76         }
77         for(;l>q[i].l;--l){
78         //这是向左 
79             int s=c[l-1];
80             rec-=(LL)(cnt[s])*(LL)(cnt[s]-1);
81             ++cnt[s];
82             rec+=(LL)(cnt[s])*(LL)(cnt[s]-1);
83         }
84         if (rec){
85             LL ret=gcd(rec,(LL)(r-l+1)*(LL)(r-l));
86             Ans[q[i].t][0]=rec/ret;
87             Ans[q[i].t][1]=((LL)(r-l+1)*(LL)(r-l))/ret;
88         } else Ans[q[i].t][0]=0,Ans[q[i].t][1]=1;//按询问顺序保存&输出 
89     }
90     F(i,1,m) printf("%lld/%lld\n",Ans[i][0],Ans[i][1]);
91     return 0;
92 }
View Code

 

posted @ 2014-12-31 19:18  Tunix  阅读(319)  评论(0编辑  收藏  举报