[BZOJ2038]:[2009国家集训队]小Z的袜子(hose)(离线莫队)
题目传送门
题目描述
作为一个生活散漫的人,小$Z$每天早上都要耗费很久从一堆五颜六色的袜子中找出一双来穿。终于有一天,小Z再也无法忍受这恼人的找袜子过程,于是他决定听天由命……
具体来说,小$Z$把这$N$只袜子从$1$到$N$编号,然后从编号$L$到$R$($L$尽管小$Z$并不在意两只袜子是不是完整的一双,甚至不在意两只袜子是否一左一右,他却很在意袜子的颜色,毕竟穿两只不同色的袜子会很尴尬)。
你的任务便是告诉小$Z$,他有多大的概率抽到两只颜色相同的袜子。当然,小$Z$希望这个概率尽量高,所以他可能会询问多个$(L,R)$以方便自己选择。
输入格式
输入文件第一行包含两个正整数$N$和$M$。
$N$为袜子的数量,$M$为小$Z$所提的询问的数量。
接下来一行包含$N$个正整数$C_i$,其中$C_i$表示第$i$只袜子的颜色,相同的颜色用相同的数字表示。
再接下来$M$行,每行两个正整数$L$,$R$表示一个询问。
输出格式
包含$M$行,对于每个询问在一行中输出分数$A/B$表示从该询问的区间$[L,R]$中随机抽出两只袜子颜色相同的概率。
若该概率为$0$则输出$0/1$,否则输出的$A/B$必须为最简分数。(详见样例)
样例
样例输入
6 4
1 2 3 3 3 2
2 6
1 3
3 5
1 6
样例输出
2/5
0/1
1/1
4/15
数据范围与提示
样例解释:
询问1:共$C_5^2=10$种可能,其中抽出两个$2$有$1$种可能,抽出两个$3$有$3$种可能,概率为$\frac{1+3}{10}=\frac{4}{10}=2/5$。
询问2:共$C_3^2=3$种可能,无法抽到颜色相同的袜子,概率为$\frac{0}{3}=0/1$。
询问3:共$C_3^2=3$种可能,均为抽出两个$3$,概率为$\frac{3}{3}=1/1$。
注:上述$C_a^b$表示组合数,组合数$C_a^b$等价于在$a$个不同的物品中选取$b$个的选取方案数。
数据规模与约定:
30%的数据中,$N,M\leqslant 5,000$;
60%的数据中,$N,M\leqslant 25,000$;
100%的数据中,$N,M\leqslant 50,000$,$1\leqslant L<R\leqslant N$,$C_i\leqslant N$。
题解
设区间中有$k$个不同的颜色,每种颜色有$s[i]$个,答案就是:$\sum \limits_{i=1}{k}\frac{C_{s_i}^2}{C_n^2}$。
之后发现只要维护$s_i^2$即可,在移动左右端点时开个桶就能简单地维护出来。
化一下上面的式子,你会发现其实答案就是:$\sum \limits_{i=1}{k}\frac{(s_i-1)\times s_i}{(n-1)\times n}$,这样在代码实现上就简单多了。
代码时刻
#include<bits/stdc++.h>
using namespace std;
struct rec
{
int l;
int r;
int id;
int pos;
}q[50001];
int n,m;
int a[50001];
int cnt[50001];
long long ans;
long long a1[50001],a2[50001];
bool cmp(rec a,rec b){return a.pos==b.pos?a.r<b.r:a.pos<b.pos;}
void upd(int x,int w)//计算答案
{
ans-=cnt[a[x]]*cnt[a[x]];
cnt[a[x]]+=w;
ans+=cnt[a[x]]*cnt[a[x]];
}
int main()
{
scanf("%d%d",&n,&m);
int t=sqrt(n);
for(int i=1;i<=n;i++)
scanf("%d",&a[i]);
for(int i=1;i<=m;i++)
{
scanf("%d%d",&q[i].l,&q[i].r);
q[i].id=i;
q[i].pos=(q[i].l-1)/t+1;
}
sort(q+1,q+m+1,cmp);
int l=1,r=0;
for(int i=1;i<=m;++i)
{
while(l<q[i].l)upd(l++,-1);
while(l>q[i].l)upd(--l,1);
while(r<q[i].r)upd(++r,1);
while(r>q[i].r)upd(r--,-1);
if(l==r){a1[i]=0,a2[i]=1;}
long long x=ans-(r-l+1);//分子
long long y=(long long)(q[i].r-q[i].l+1)*(q[i].r-q[i].l);//分母
long long g=__gcd(x,y);//约分
a1[q[i].id]=x/g,a2[q[i].id]=y/g;
}
for(int i=1;i<=m;i++)
cout<<a1[i]<<'/'<<a2[i]<<endl;
return 0;
}