莫队入门题:小Z的袜子心得

莫队算法,感觉与trajan有异曲同工之妙。

通过合理安排询问,来使用时减少,妙不可言。

题目:小Z的袜子

题意:

   给出一个长度为n的序列。m次询问在li到ri随机选取两个能凑成一对的几率。

   需转化成分数输出,若几率为0,则输出0/1。

样例输入:

6 4
1 2 3 3 3 2
2 6
1 3
3 5
1 6
样例输出:

2/5
0/1
1/1
4/15

解法:

   通过查找规律,不难发现对于每一个询问答案为  询问中每个元素的平方和-(询问区间元素数量)/(询问区间元素数量)*(询问区间覆盖长度)

   于是可以将其询问通过排序集中在几个块中,减少暴力操作数量。

代码:

#include <cmath>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
#define ll long long
#define read(x) scanf("%d",&x)
#define For(x,deep,top) for(x=deep;x<=top;x++)
using namespace std;
const int NM = 50020;
int s[NM],pos[NM];
int n,m,block;
ll rt[NM];
ll ans;

struct question
{
    int l,r,v;
    ll s,m;
} qs[NM];

inline ll gcd (ll a,ll b)
{
    return b == 0 ? a : gcd(b,a%b) ;
}

ll mt(ll x)
{
    return x*x;
}

inline bool cmp1(question a,question b)     //以块为第一关键字,区间右端点为第二关键字排序
{
    if (pos[a.l] == pos[b.l]) return a.r<b.r;
    return a.l < b.l;
}

inline bool cmp2(question a,question b)
{
    return a.v < b.v;
}

inline void update(int p,int add)
{
    ans-=mt(rt[s[p]]);
    rt[s[p]]+=add;
    ans+=mt(rt[s[p]]);
}

int main()
{
    int k,p;
    register int i;                         //玄学加速
    read(n),read(m);
    block = int(sqrt(n));
    For(i,1,n) read(s[i]);
    For(i,1,n) pos[i]=(i-1)/block+1;        //分块
    For(i,1,m)
    {
        read(k),read(p);
        qs[i].l = k;
        qs[i].r = p;
        qs[i].v = i;
    }
    sort(qs+1,qs+1+m,cmp1);                 //通过合理处理顺序减少暴力修改操作
    for (register int j=1,l=1,r=0;j<=m;j++)
    {
        for (;r<qs[j].r;r++) update(r+1,1); //调整右端位置
        for (;r>qs[j].r;r--) update(r,-1);
        for (;l<qs[j].l;l++) update(l,-1);  //调整左端位置
        for (;l>qs[j].l;l--) update(l-1,1);
        if (qs[j].l-qs[j].r==0)
        {
            qs[j].s=0;
            qs[j].m=1;
            continue;
        }
        qs[j].s=(ll)(ans-qs[j].r+qs[j].l-1);
        qs[j].m=(ll)(qs[j].r-qs[j].l+1)*(qs[j].r-qs[j].l);
        ll t = gcd(qs[j].s,qs[j].m);
        qs[j].s/=t;qs[j].m/=t;
    }
    sort(qs+1,qs+1+m,cmp2);                 //转换原来顺序输出答案
    For(i,1,m)
     printf("%I64d/%I64d\n",qs[i].s,qs[i].m);
    return 0;
}

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值