题目引入
https://ac.nowcoder.com/acm/contest/1034/C
思路:
我们要查询的区间范围为[l,r],设这个区间的长度为n,假设袜子是选取完后,又放回的,也就是说一只袜子可以选取两次,那么表示概率的分母就可以表示为:nn,设这个区间里这种颜色的袜子的总数为sum,选取到这只袜子的概率就为sumsum/n*n。
当然这不符合题目所要求的,但是我们可以用这种比较简单的方法,先求出可放回的概率,再对分子分母进行操作。
用分子减去区间的总长度,是不是就是减去了自身选取自身的概率:sumsum-n,分母变成n(n-1)就是题目所要求的概率了。
基本莫队:
注意:
块值相等的按照right的大小来排序,块值不相等的按照left大小来排序。
ll cmp(KNIGHT a,KNIGHT b){
if(block[a.left]==block[b.left])
return a.right<b.right;
return a.left<b.left;
}
代码:
#include <bits/stdc++.h>
#define MAXN 50005
#define ll long long
using namespace std;
struct KNIGHT{
ll left;
ll right;
ll id;
}p[MAXN];
ll c[MAXN],block[MAXN],sum[MAXN];
ll ansa[MAXN],ansb[MAXN];
ll gcd(ll x,ll y){
if(y!=0)
return gcd(y,x%y);
return x;
}
ll cmp(KNIGHT a,KNIGHT b){
if(block[a.left]==block[b.left])
return a.right<b.right;
return a.left<b.left;
}
int main(){
int n,m;
while(scanf("%d%d",&n,&m)!=EOF){
int radical = sqrt(n);
for(int i=1;i<=n;i++){
scanf("%lld",&c[i]);
block[i] = (i-1)/radical+1;
}
for(int i=1;i<=m;i++){
scanf("%lld%lld",&p[i].left,&p[i].right);
p[i].id = i;
}
ll left = 1,right = 0;
sort(p+1,p+m+1,cmp);
ll temp = 0 ;
for(int i=1;i<=m;i++){
while(left<p[i].left){
temp -= sum[c[left]]*sum[c[left]];
sum[c[left]]--;
temp += sum[c[left]]*sum[c[left]];
left++;
}
while(left>p[i].left){
left--;
temp -= sum[c[left]]*sum[c[left]];
sum[c[left]]++;
temp += sum[c[left]]*sum[c[left]];
}
while(right>p[i].right){
temp -= sum[c[right]]*sum[c[right]];
sum[c[right]]--;
temp += sum[c[right]]*sum[c[right]];
right--;
}
while(right<p[i].right){
right++;
temp -= sum[c[right]]*sum[c[right]];
sum[c[right]]++;
temp += sum[c[right]]*sum[c[right]];
}
ll a = temp - (p[i].right-p[i].left+1);
ll b = (p[i].right-p[i].left+1)*(p[i].right-p[i].left);
ll tep = gcd(a,b);
ansa[p[i].id] = a/tep;
ansb[p[i].id] = b/tep;
}
for(int i=1;i<=m;i++){
printf("%lld/%lld\n",ansa[i],ansb[i]);
}
}
}