题目链接
题意:
从1到n有n个数,每次询问给你一个区间
,问你从区间中随机选两个数,选到两个相同的数的概率(两次选到的数下标不能相同),答案输出最简分数(若l=r,则输出0/1)。
题解:
假设在区间
中有若干种不同的数,它们出现的次数分别是a、b、c…,那么第一次有
种选择,第二次有
种选择,根据乘法原理,两次一共
种选择。而第一次选到出现了a次的那个数的概率是
,第二次是在第一次已经选了一个该数的情况下再选到该数才行,所以第二次的概率应该是
,那么根据乘法原理,在区间内两次选到的相同的那个数一共在区间里出现了a次的话,概率是
。而有若干种数出现,那么答案就是
。要保证复杂度,那么一个想法就是我们不可能每次都把这个式子计算一遍,那么我们就考虑每次询问答案会在之前的基础上有哪些变动。我们发现,分母在给出询问时就可以
计算出了,那么我们就考虑分子。我们把分子展开,得到
,而由于区间内有
个数,那么所有数在区间内出现的次数之和
就等于
。但是怎么维护
呢?
首先根据数据范围,直接暴力肯定不行,而又不满足线段树的区间的可合并性,那么我们就不妨考虑根号算法,于是就考虑使用莫队。我们知道,莫队是使用条件就是由
的答案得到
的答案和
的答案需要
或者
的时间即可,那么我们就考虑这个问题需要什么样的复杂度。我们考虑区间的左端点或者右端点移动1个位置,那么相当于在原区间的基础上,新加上或删去的那个数的出现次数改变了1,那么我们记录每个数在当前区间出现了多少次,我们先减去原来出现次数的平方(因为出现次数是
,维护的是
),再将出现次数加1或减1,加上新的出现次数的平方即可。
最后要输出最简分数就除以gcd即可。
代码:
#include <bits/stdc++.h>
using namespace std;
int n,m,c[50010],pos[50010],sz;
long long sum,tot[50010];
struct node
{
int l,r,id;
long long a,b;
}a[100010];
int cmp1(node x,node y)
{
if(pos[x.l]==pos[y.l])
return x.r<y.r;
else
return x.l<y.l;
}
int cmp2(node x,node y)
{
return x.id<y.id;
}
inline void add(int x)
{
sum-=(long long)tot[x]*tot[x];
++tot[x];
sum+=(long long)tot[x]*tot[x];
}
inline void del(int x)
{
sum-=(long long)tot[x]*tot[x];
--tot[x];
sum+=(long long)tot[x]*tot[x];
}
inline long long gcd(long long x,long long y)
{
return y?gcd(y,x%y):x;
}
int main()
{
scanf("%d%d",&n,&m);
for(int i=1;i<=n;++i)
scanf("%d",&c[i]);
for(int i=1;i<=m;++i)
{
scanf("%d%d",&a[i].l,&a[i].r);
a[i].id=i;
}
sz=sqrt(n);
for(int i=1;i<=n;++i)
pos[i]=(i-1)/sz+1;
sort(a+1,a+m+1,cmp1);
int l=1,r=0;
for(int i=1;i<=m;++i)
{
while(l<a[i].l)
del(c[l++]);
while(l>a[i].l)
add(c[--l]);
while(r<a[i].r)
add(c[++r]);
while(r>a[i].r)
del(c[r--]);
if(a[i].l==a[i].r)
{
a[i].a=0;
a[i].b=1;
continue;
}
a[i].a=sum-(a[i].r-a[i].l+1);
a[i].b=(long long)(a[i].r-a[i].l+1)*(a[i].r-a[i].l);
long long d=gcd(a[i].a,a[i].b);
a[i].a/=d;
a[i].b/=d;
}
sort(a+1,a+m+1,cmp2);
for(int i=1;i<=m;++i)
printf("%lld/%lld\n",a[i].a,a[i].b);
return 0;
}