题意:
给出一个长度为n的序列,每次询问一个区间[l,r];
查询在这个区间中取出两个数恰好相等的概率;
每个数大小在[0,n]内,概率用既约分数表示;
题解:
考虑一个区间的答案,显然是合法方案数/取数的所有可能;
也就是 ∑C[同种数字个数][2]/C[r-l+1][2];
但是这个东西对一次询问的处理复杂度是O(r-l+1)的;
那么考虑上莫队算法,处理这样的区间问题;
很容易发现每次修改边界可以做到O(1)完成;
void update(int x,int op) { now-=C[s[a[x]]][2]; s[a[x]]+=op; now+=C[s[a[x]]][2]; }
C[i][j]是组合数,s[x]是当前区间x的数量,now是当前答案;
然后将询问排序处理,第一关键字左端点所在块,第二关键字右端点;
当块的大小为√n时,可以证明复杂度不会超过O(n√n);
然后每个区间转移到下一个区间就是有复杂度保证的暴力咯;
码量似乎不算太巨大,思想也比较简单,很神的暴力算法;
代码:
#include<math.h>
#include<stdio.h>
#include<string.h>
#include<algorithm>
#define N 51000
using namespace std;
struct query
{
int pos,l,r,no;
}Q[N];
int a[N],s[N],ans_up[N],ans_do[N],C[N][3],now;
bool cmp(query a,query b)
{
if(a.pos==b.pos)
return a.r<b.r;
return a.pos<b.pos;
}
int gcd(int a,int b)
{
if(!a||!b) return a?a:b;
int t=a%b;
while(t)
{
a=b,b=t;
t=a%b;
}
return b;
}
void update(int x,int op)
{
now-=C[s[a[x]]][2];
s[a[x]]+=op;
now+=C[s[a[x]]][2];
}
int main()
{
int n,m,k,i,j,l,r;
scanf("%d%d",&n,&m);
int bk=sqrt(n);
for(i=1;i<=n;i++)
scanf("%d",a+i);
for(i=0;i<=n;i++)
{
C[i][0]=1;
for(j=1;j<=2;j++)
C[i][j]=C[i-1][j]+C[i-1][j-1];
}
for(i=1;i<=m;i++)
{
scanf("%d%d",&Q[i].l,&Q[i].r);
Q[i].no=i,Q[i].pos=Q[i].l/bk;
}
sort(Q+1,Q+m+1,cmp);
l=0,r=0,now=0,s[50001]=1,a[0]=50001;
for(i=1;i<=m;i++)
{
while(l<Q[i].l) update(l++,-1);
while(l>Q[i].l) update(--l, 1);
while(r<Q[i].r) update(++r, 1);
while(r>Q[i].r) update(r--,-1);
ans_up[Q[i].no]=now;
ans_do[Q[i].no]=C[r-l+1][2];
}
for(i=1;i<=m;i++)
{
k=gcd(ans_up[i],ans_do[i]);
printf("%d/%d\n",ans_up[i]/k,ans_do[i]/k);
}
return 0;
}