莫队算法 -- BZOJ2038小Z的袜子

题目链接

分析:

我们将问题抽象为:从某个已知区间里询问,任取两个数,这两个数相等的概率。

假设这个区间为[L,R],这个区间里一共有n个不同的数字a1,a2,...,an,n个数字对应其个数b1,b2,...,bn,那么可以根据组合排列,求出这个概率。

P = \cfrac{\cfrac{a(a-1)}{2}+\cfrac{b(b-1)}{2}+\cfrac{c(c-1)}{2}+...}{\cfrac{(R-L+1)(R-L)}{2}} = \cfrac{\cfrac{\sum_{i=1}^{n} bi(bi-1)}{2}}{\cfrac{(R-L+1)(R-L)}{2}}

关于这个公式的解释:其实就是排列组合的结果,参考公式  组合数计算的公式

从每一个数字集(例如2,2,2)中随机抽取两个数,这两个数有多少种相等的情况,n个不同的数字集这样处理之后加起来的和就是分子。

从区间内所有数字中随机抽取两个数,有多少种情况就是分母。

理解上述公式之后,化简得   \cfrac{a^2+b^2+c^2+...x^2-(a+b+c+d+.....)}{(R-L+1)(R-L)}

即:\cfrac{a^2+b^2+c^2+...x^2-(R-L+1)}{(R-L+1)(R-L)}

得到这个公式之后,那么就得到题目所要求的结果,所以我们对应的求出分子和分母就好了,因为要最简化,所以还需要写一个GCD函数来化简分式。

时间复杂度证明:

参考自:https://www.cnblogs.com/hzf-sbit/p/4056874.html

右端点移动:
       同一个块里面的右端点转移情况
由于一个块里面的询问都按右端点排序
所以右端点在一个块里面最多移动n次
\sqrt{n}个块,那么同一个块内的右端点移动最多就是n\sqrt{n}
       从一个块到另一个块导致的右端点变化
最坏情况,右端点由n到1,那么移动n次,有\sqrt{n}个块
从一个块到另一个块的事件只会发生\sqrt{n}
所以这种右端点移动的次数也是n\sqrt{n}


左端点移动:
    同一个块里面
由于左端点都在一个长度为\sqrt{n}​​​​​​​的区间里面
所以在同一块里面移动一次,左端点最多变化\sqrt{n}
总共有n个询问
同一块里面的移动最多n次
那么同一个块里面的左端点变化最多是n\sqrt{n}
    考虑跨越块
每由第i个块到第i+1个块,左端点最坏情况加上\sqrt{n}
总共能加上\sqrt{n}
所以跨越块导致的左端点移动是O(n)的


综上,分块做法的复杂度是O(n\sqrt{n})

实现代码:

#include<bits/stdc++.h>
#define fo(i,a,b) for(int i=a;i<=b;i++)
#define mem(a,b) memset(a,b,sizeof(a))
#define ll long long
using namespace std;
const int N=50003;
int n,m,col[N],unit,Be[N];
ll sum[N],ans;
/*
    变量说明:
    col[]存每个袜子的颜色
    sum[]存不同颜色的数量
    unit表示分块之后的块数
    Be[i]表示i属于哪个块
*/

struct node
{
    int l,r,id;
    ll a,b;
} q[N];
ll sq(ll x)
{
    return x*x;
}
ll GCD(ll a,ll b)
{
    return b==0?a:GCD(b,a%b);
}
bool cmp(node a,node b)
{
    return Be[a.l]==Be[b.l]?a.r<b.r:a.l<b.l;
}
bool CMP(node a,node b)
{
    return a.id<b.id;
};
void revise(int x,int add)
{
    ans-=sq(sum[col[x]]);///减去之前的
    sum[col[x]]+=add;///sum的变化依赖于l和r的移动方向
    ans+=sq(sum[col[x]]);///加上变化之后的
}
int main()
{
    scanf("%d%d",&n,&m);
    unit=sqrt(n);
    fo(i,1,n)
    {
        scanf("%d",&col[i]);
        Be[i]=i/unit+1;
    }
    fo(i,1,m)
    {
        scanf("%d%d",&q[i].l,&q[i].r);
        q[i].id=i;
    }

    sort(q+1,q+m+1,cmp);

    int l=1,r=0;
    fo(i,1,m)
    {
        while(l<q[i].l)revise(l,-1),l++;///l右移,范围缩小
        while(l>q[i].l)revise(l-1,1),l--;///l左移,范围扩大
        while(r<q[i].r)revise(r+1,1),r++;///r右移,范围扩大
        while(r>q[i].r)revise(r,-1),r--;///r左移,范围缩小

        if(q[i].l==q[i].r)///区间里只有一只袜子,概率为0
        {
            q[i].a=0;///分子
            q[i].b=1;///分母
        }
        else
        {
            q[i].a=ans-(q[i].r-q[i].l+1);
            q[i].b=1LL*(q[i].r-q[i].l+1)*(q[i].r-q[i].l);
            ///1LL是将运算结果转化为long long
            ll gcd=GCD(q[i].a,q[i].b);///最简化
            q[i].a/=gcd;
            q[i].b/=gcd;
        }
    }
    sort(q+1,q+m+1,CMP);///处理之后按输入顺序给区间排序
    fo(i,1,m)
        printf("%I64d/%I64d\n",q[i].a,q[i].b);
    return 0;
}//By Mic_H

 

Blog For Reference:

https://www.cnblogs.com/P6174/p/7723856.html

https://www.cnblogs.com/Paul-Guderian/p/6933799.html

 

FYI:

这个代码是在我看了上述两个博客写的,并没有放在OJ上跑过,仅供理解这个算法用,如有出错,还望各位大佬指正。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值