惯例,传送门:P1494 [国家集训队]小Z的袜子
然后就开讲啦,莫队板子题……
[警告:本文中等长度,请耐心阅读]
1.首先要明白:对于一个询问我们要求出什么?怎么求?
要知道概率,首先得知道情况数,故用一个数组sum[]来存储[l,r]区间内每种颜色袜子的只数.那么就有人要问了,每个区间sum[]的取值是不一样的,怎样存储呢?
先别急,证明一下一个询问的一般情况再做探究!
[l,r]区间内,由题意可知袜子不能放回,令该询问的长度r-l+1=len,
则抽到一对颜色i的概率为(sum[i]/len)*[(sum[i]-1)/(len-1)],
整理得概率为(sum[i]2-sum[i])/[len*(len-1)],
令共有n种颜色,那么抽到所有颜色的总和即为[(sum[1]2-sum[1])+(sum[2]2-sum[2])+…+(sum[n]2-sum[n])]/(len*(len-1)),
拆开括号得[(∑i1<=i<=nsum[i]2)-(∑i1<=i<=nsum[i])]/[len*(len-1)].
这时,我们有了一个惊人的发现!
据定义易得,∑i1<=i<=nsum[i]=len,那么最终公式出炉:[(∑i1<=i<=nsum[i]2)-len]/[len*(len-1)].分数线前的部分即为输出答案的分子,线后即为分母.
这下,一个询问就做完啦!
2.接下来,就要思考刚才提出的问题,sum[]的变化.
探究:l向右移动一格时,公式如何变化?
此时,sum[l移动前的颜色]--,答案中与该颜色相关的∑i1<=i<=nsum[i]2
划重点:实质上只会在sum[l移动前的颜色]--之前先减小sum[l移动前的颜色]2,之后再增大sum[l移动前的颜色]2,
故只需计算一个[l,r]的结果.对于每次询问的变化,可以用暴力跳l,r的方式解决.
3.如何减小时间复杂度?
由1,2两点推出的结论是完全正确的,但是,你很快就能发现T了,为什么呢?
思考:当给出的询问完全无序时,以上做法将会暴跳指针,造成大量时间浪费,于是就有了一个轻巧的办法:分块.
我们人为控制,将块的大小设为T.
那么就可以按照左端点从小到大排序,在每块内部按照左端点从小到大按照左端点从小到大重新按照右端点从小到大排序.
这样安排后,对于每块起点枚举m个答案分别计算结果,两询问左端点变化不超过2T,又m个颜色,故时间O(mT),
右端点递增,总量为n次,则右端点时间O(n2/T),又n,m同级,故总时间复杂度为O(nT+n2/T),
根据相关数学知识可得当T=sqrt(n)时,时间复杂度最小,为O(nsqrt(n)*sqrt(n)).
那么,这道题才算真正的做完了,撒花!
等一下,真的完了?
还有点小细节哦!
1.注意乘积可能爆int,随手long long养成好习惯
2.注意当结果为0时要求特殊输出
这下真的说完了!
真没了,回去吧!
1 //Written By Peter0701 2 #include<bits/stdc++.h> 3 using namespace std; 4 int n,m,a[50005],t,pos[50005]; 5 long long sum[50005],ans; 6 inline int read() 7 { 8 int ret=0,f=1; 9 char ch=getchar(); 10 while(ch>'9'||ch<'0') 11 { 12 if(ch=='-') 13 f=-1; 14 ch=getchar(); 15 } 16 while(ch<='9'&&ch>='0') 17 { 18 ret=(ret<<1)+(ret<<3)+ch-'0'; 19 ch=getchar(); 20 } 21 return ret*f; 22 } 23 struct node 24 { 25 int l,r,num; 26 long long up,down; 27 }q[50005]; 28 long long gcd(long long a,long long b) 29 { 30 while(b^=a^=b^=a%=b); 31 return a; 32 } 33 inline bool cmp1(node a,node b) 34 { 35 return pos[a.l]==pos[b.l]?a.r<b.r:a.l<b.l; 36 } 37 inline bool cmp2(node a,node b) 38 { 39 return a.num<b.num; 40 } 41 inline void revise(int x,int add) 42 { 43 ans-=sum[a[x]]*sum[a[x]]; 44 sum[a[x]]+=add; 45 ans+=sum[a[x]]*sum[a[x]]; 46 } 47 int main() 48 { 49 n=read(); 50 m=read(); 51 t=sqrt(n); 52 for(register int i=1;i<=n;i++) 53 { 54 a[i]=read(); 55 pos[i]=i/t+1; 56 } 57 for(register int i=1;i<=m;i++) 58 { 59 q[i].l=read(); 60 q[i].r=read(); 61 q[i].num=i; 62 } 63 sort(q+1,q+m+1,cmp1); 64 int l=1,r=0; 65 for(register int i=1;i<=m;i++) 66 { 67 while(l<q[i].l) 68 revise(l,-1),l++; 69 while(l>q[i].l) 70 revise(l-1,1),l--; 71 while(r<q[i].r) 72 revise(r+1,1),r++; 73 while(r>q[i].r) 74 revise(r,-1),r--; 75 if(q[i].l==q[i].r) 76 { 77 q[i].up=0; 78 q[i].down=1; 79 continue; 80 } 81 q[i].up=ans-(q[i].r-q[i].l+1); 82 q[i].down=1LL*(q[i].r-q[i].l+1)*(q[i].r-q[i].l); 83 long long bei=gcd(q[i].up,q[i].down); 84 q[i].up/=bei;q[i].down/=bei; 85 } 86 sort(q+1,q+m+1,cmp2); 87 for(register int i=1;i<=m;i++) 88 printf("%lld/%lld\n",q[i].up,q[i].down); 89 return 0; 90 }