2009国家集训队 小Z的袜子【莫队入门】

题目链接: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);
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值