2038: [2009国家集训队]小Z的袜子(hose)
Description
作为一个生活散漫的人,小Z每天早上都要耗费很久从一堆五颜六色的袜子中找出一双来穿。终于有一天,小Z再也无法忍受这恼人的找袜子过程,于是他决定听天由命…… 具体来说,小Z把这N只袜子从1到N编号,然后从编号L到R(L 尽管小Z并不在意两只袜子是不是完整的一双,甚至不在意两只袜子是否一左一右,他却很在意袜子的颜色,毕竟穿两只不同色的袜子会很尴尬。 你的任务便是告诉小Z,他有多大的概率抽到两只颜色相同的袜子。当然,小Z希望这个概率尽量高,所以他可能会询问多个(L,R)以方便自己选择。
Input
输入文件第一行包含两个正整数N和M。N为袜子的数量,M为小Z所提的询问的数量。接下来一行包含N个正整数Ci,其中Ci表示第i只袜子的颜色,相同的颜色用相同的数字表示。再接下来M行,每行两个正整数L,R表示一个询问。
Output
包含M行,对于每个询问在一行中输出分数A/B表示从该询问的区间[L,R]中随机抽出两只袜子颜色相同的概率。若该概率为0则输出0/1,否则输出的A/B必须为最简分数。(详见样例)
Sample Input
Sample Output
Source
这是一道极为经典的题目。注意到一点,如果我们有计算完[L, R]时的“中间变量”(在本题为每个数出现的次数),那么[L - 1, R]、[L + 1, R]、[L, R - 1]、[L, R + 1]都能够在“中间变量”的“基本操作时间复杂度”(O(1)orO(log n)……)得出。
例如在此题中,我们如果列出算式,可以轻易发现[L,R]对应的答案通过区间内各颜色数目的平方和快速计算,如果辅助存储区间内各颜色数目,那么O(1)的比邻状态转移就能够实现。
以此便可介入此题。发现对于一个询问,可以从上一个询问开始通过移动区间得到现有答案。但是,这样很明显不优。状态间的转移即是左端点与右端点的位移,复杂度就是abs(L1 - L2) + abs(R1 - R2)。如果出题人足够邪恶,以至于一次转移是O(n)的,总的算下来O(nq)绝对无法接受。(但我做了实验并没有TLE)。
当然,有人会说,这道题并没有强制在线,我们可以按照恰当的顺序去解决每个询问。例如,按照左端点从小到大排序,如果左端点相同再右端点从小到大排序。这样确实似乎可以,但出题人也可以乱卡,让右端点乱跳,一次转移同样可以做到O(n)。
于是,就有人强推曼哈顿最小生成树(可以让顺序完美),但代码复杂度太高,同样也是O(nlogn)的复杂度。我们信息学竞赛不是“完美主义”,很有可能因小失大。
于是“莫队”(因为比赛常常当队长)说,可以把左端点的排序“模糊处理”,允许在一定区间内的左端点其右端点单调递增。一定区间?这大概就是分块了。我们并不需要去专门维护这个块,只是在排序时用到。假设块长O(k),而一个块右端点最多跳O(n),有O(n/k)个块,故右端点最多跳O(n^2/k)次。左端点每次最多跳O(k)次,故左端点最多跳O(nk)次。
我们发现,左右端点乱跳次数的乘积是相对一定的,为O(n^3),所以若k=sqrt(n),结果最优,复杂度是O(n*sqrt(n))的。
但是,话说回来,这类题目为什么不能使用线段树一类的数据结构?理由很简单,颜色太多了。但据说有很强的方法,反正我不会。
最后说一句,这道题要A特别简单。注:“莫队算法”=分块离线暴力
Type | 直接按照顺序做 | 无脑排序 | 分块排序 |
Time | 12228 ms | 5680 ms |
568 ms |
当然,可以只sort一次,能开int就开int。904 ms-〉812 ms-〉764 ms-〉568 ms
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 /**************************************************************
2 Problem: 2038
3 User: Doggu
4 Language: C++
5 Result: Accepted
6 Time:568 ms
7 Memory:2592 kb
8 ****************************************************************/
9
10 #include <cstdio>
11 #include <algorithm>
12 #include <cmath>
13 using namespace std;
14 template<class T>inline void readin(T &res) {
15 static char ch;T flag=1;
16 while((ch=getchar())<'0'||ch>'9')if(ch=='-')flag=-1;
17 res=ch-48;
18 while((ch=getchar())>='0'&&ch<='9')res=(res<<1)+(res<<3)+ch-48;
19 res*=flag;
20 }
21 const int N = 50010;
22 const int Q = 50010;
23 int n, q, K, aa[N], num[N];
24 struct data{
25 int L, R, ord;
26 bool operator< (const data &rhs) const {return L/K==rhs.L/K?R<rhs.R:L<rhs.L;}
27 }que[Q];
28 long long x[N], y[N];inline long long gcd(long long a,long long b) {if(b==0) return a;return gcd(b,a%b);}
29 int main() {
30 readin(n);readin(q);K=floor(sqrt(n));
31 for( int i = 1; i <= n; i++ ) readin(aa[i]);
32 for( int i = 1; i <= q; i++ ) readin(que[i].L),readin(que[i].R),que[i].ord=i;
33 sort(que+1,que+q+1);
34 int L = 1, R = 0, tot = 0;
35 long long xs, ys, temp;
36 for( int i = 1; i <= q; i++ ) {
37 while(R<que[i].R) {
38 R++;
39 tot+=2*num[aa[R]]+1;
40 num[aa[R]]++;
41 }
42 while(que[i].R<R) {
43 num[aa[R]]--;
44 tot-=2*num[aa[R]]+1;
45 R--;
46 }
47 while(que[i].L<L) {
48 L--;
49 tot+=2*num[aa[L]]+1;
50 num[aa[L]]++;
51 }
52 while(L<que[i].L) {
53 num[aa[L]]--;
54 tot-=2*num[aa[L]]+1;
55 L++;
56 }
57 xs=tot-(R-L+1);ys=(long long)(R-L+1)*(R-L);temp=gcd(xs,ys);
58 x[que[i].ord]=xs/temp;y[que[i].ord]=ys/temp;
59 }
60 //sort(que+1,que+q+1,cmp2);
61 for( int i = 1;i <= q; i++ ) printf("%lld/%lld
",x[i],y[i]);
62 return 0;
63 }