题目链接:https://www.lydsy.com/JudgeOnline/problem.php?id=2038
题意:
具体来说,小Z把这N只袜子从1到N编号,然后从编号L到R,他有多大的概率抽到两只颜色相同的袜子。询问多个(L,R)随机抽出两只袜子颜色相同的概率。若该概率为0则输出0/1,否则输出的A/B必须为最简分数。
分析:
莫队的优化基于分块思想:对于两个询问,若在其l在同块,那么将其r作为排序关键字,若l不在同块,就将l作为关键字排序(这就是双关键字)。
代码:
#include<bits/stdc++.h>
using namespace std;
#define ll long long
const int N=50055;
int b[N];///属于哪一个块
int col[N];
int ans,n,m;
ll sum[N];
struct node
{
int l,r,id;
ll s,ss;///s是分子,ss是分母
}v[N];
bool com1(node aa,node bb)
{
return {b[aa.l]==b[bb.l]?aa.r<bb.r:aa.l<bb.l};
}
bool com2(node aa,node bb)
{
return aa.id<bb.id;
}
ll GCD(ll a,ll b)
{
while(b^=a^=b^=a%=b);
return a;
}
ll get(ll x)
{
return x*(x-1);
}
void update(int x,int add)
{
ans-=get(sum[col[x]]);
sum[col[x]]+=add;
ans+=get(sum[col[x]]);
}
int main()
{
int i,j,Size,l,r;
scanf("%d%d",&n,&m);
Size=sqrt(n);///每一个块的大小
for(i=1;i<=n;i++)
{
scanf("%d",&col[i]);
b[i]=i/Size+1;
}
for(i=1;i<=m;i++)
{
scanf("%d%d",&v[i].l,&v[i].r);
v[i].id=i;
}
sort(v+1,v+1+m,com1);
l=1;
r=0;///l包含的下界,r是包含的上界,r<l此时区间为空
ans=0;
for(i=1;i<=m;i++)
{
while(l<v[i].l)///将区间中的点l除掉
{
update(l,-1);
l++;
}
while(l>v[i].l)///将区间中的l-1加上,此时l已经在区间中
{
update(l-1,1);
l--;
}
while(r<v[i].r)///将区间中的点r+1加上,此时r已经在里面了
{
update(r+1,1);
r++;
}
while(r>v[i].r)///将区间中的r去掉
{
update(r,-1);
r--;
}
if(v[i].l==v[i].r){v[i].s=0;v[i].ss=1;continue;}
v[i].s=ans;
v[i].ss=(1LL)*(v[i].r-v[i].l)*(v[i].r-v[i].l+1);
ll gg;
gg=GCD(v[i].s,v[i].ss);
if(gg)
{
v[i].s=v[i].s/gg;
v[i].ss=v[i].ss/gg;
}
}
sort(v+1,v+1+m,com2);
for(i=1;i<=m;i++)
printf("%lld/%lld\n",v[i].s,v[i].ss);
}