洛谷1494 BZOJ2038 小Z的袜子 莫队 乘法原理 概率

题目链接
题意:
从1到n有n个数,每次询问给你一个区间 [ l , r ] ,问你从区间中随机选两个数,选到两个相同的数的概率(两次选到的数下标不能相同),答案输出最简分数(若l=r,则输出0/1)。

题解:
假设在区间 [ l , r ] 中有若干种不同的数,它们出现的次数分别是a、b、c…,那么第一次有 r l + 1 种选择,第二次有 r l 种选择,根据乘法原理,两次一共 ( r l + 1 ) ( r l ) 种选择。而第一次选到出现了a次的那个数的概率是 a r l + 1 ,第二次是在第一次已经选了一个该数的情况下再选到该数才行,所以第二次的概率应该是 a 1 r l ,那么根据乘法原理,在区间内两次选到的相同的那个数一共在区间里出现了a次的话,概率是 a ( a 1 ) ( r l + 1 ) ( r l ) 。而有若干种数出现,那么答案就是 a ( a 1 ) + b ( b 1 ) + c ( c 1 ) + . . . ( r l + 1 ) ( r l ) 。要保证复杂度,那么一个想法就是我们不可能每次都把这个式子计算一遍,那么我们就考虑每次询问答案会在之前的基础上有哪些变动。我们发现,分母在给出询问时就可以 O ( 1 ) 计算出了,那么我们就考虑分子。我们把分子展开,得到 a 2 + b 2 + c 2 + . . a b c . . . ,而由于区间内有 r l + 1 个数,那么所有数在区间内出现的次数之和 a + b + c + . . . 就等于 r l + 1 。但是怎么维护 a 2 + b 2 + c 2 + . . . 呢?
首先根据数据范围,直接暴力肯定不行,而又不满足线段树的区间的可合并性,那么我们就不妨考虑根号算法,于是就考虑使用莫队。我们知道,莫队是使用条件就是由 [ l , r ] 的答案得到 [ l + 1 ( 1 ) , r ] 的答案和 [ l , r + 1 ( 1 ) ] 的答案需要 O ( 1 ) 或者 O ( l o g n ) 的时间即可,那么我们就考虑这个问题需要什么样的复杂度。我们考虑区间的左端点或者右端点移动1个位置,那么相当于在原区间的基础上,新加上或删去的那个数的出现次数改变了1,那么我们记录每个数在当前区间出现了多少次,我们先减去原来出现次数的平方(因为出现次数是 a ,维护的是 a 2 ),再将出现次数加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;
}

猜你喜欢

转载自blog.csdn.net/forever_shi/article/details/81172819