莫队算法初探

莫队,是一种算法,是国家队长莫涛发明的orz,
它是来解决什么问题的呢?划重点

我们常常会遇到这样一类题:给你一个\([1,n]\)的序列,每次查询\([l,r]\)的一些信息(例如不同数的个数等),这个时候,我们就可以使用莫队来解决。

注意,莫队是一种离线算法。

我们考虑,当我们知道\([l1,r1]\)的值时,我们要计算出\([l2,r2]\)的值。

我们可以\(O(1)\)地算出\([l1-1,r1],[l1+1,r1],[l1,r1-1],[l1,r1+1]\)的值,那么,我们就可以在\(O(|l2-l1|+|r2-r1|)\)的时间复杂度内完成这次转移,即算出答案。也就是说,我们可以省去这两个询问区间的交集所需要的时间。

而我们现在要做的,就是通过一种特定的排序方式,使得对于一个询问集合\(q\),使得\(\sum_{i=1}^n(|q[i].l-q[i-1].l|+q[i].r-q[i-1].r|)\)达到我们可以接受的值;

我们考虑用分块来优化,以左端点所在的块为第一关键字,右端点的值为第二关键字来排序,这样的时间复杂度就降到了\(O(n\sqrt{n})\)

证明?我也不会啊

根据dllxl的认(ka)真(chang)教(ji)导(qiao),我们在comp函数中可以进行优化(奇偶块优化),即如果是奇数块就右端点升序排列,否则就降序排列:

inline bool comp(Node a,Node b)
{
    return belong[a.l]^belong[b.l] ? belong[a.l] < belong[b.l] : belong[a.l]&1 ? a.r<b.r : a.r>b.r; 
}

这样会快很多,还有就是block的大小跟代码速度有巨大关系,根据dllxl等人的不懈努力,确定\(block=n/(m*2/3)\)时,是最快的(目前);

讲讲题吧:

小Z的袜子

我们考虑对于一个区间\([l,r]\),设\(cnt[x]\)为此区间中颜色为x的个数,则选到相同袜子的情况有\(\sum C_{cnt[x]}^2\)种,而任意选两个,一共有\(C_{r-l+1}^2\)种选法,两者相除即为答案。

由公式
\(C_n^2=\frac{n!}{2!*(n-2)!}=\frac{n*(n-1)}{2}\)并整理得

\(len=r-l+1\),则答案为

\(\frac{\sum cnt[x]^2-\sum cnt[x]}{len\times(len-1)}\)

\(=\frac{\sum cnt[x]^2-len}{len\times(len-1)}\)

而我们要维护的,就是\(\sum cnt[x]^2(x\in col(l,r))\);

用莫队:

考虑转移的时候,如果加入一个元素x,则\(ans+=cnt[x]*2+1,cnt[x]++\)
(因为\((cnt[x]+1)^2=cnt[x]^2+2*cnt[x]+1\)

同理,当删除一个元素x,则\(ans-=cnt[x]*2-1,cnt[x]--\)(因为\((cnt[x]-1)^2=cnt[x]^2-2*cnt[x]+1\))

我们可以写两个函数:

inline void add(int x) {ans+=cnt[x]*2+1,cnt[x]++;}
inline void del(int x) {ans-=cnt[x]*2-1,cnt[x]--;}

然后,我们每转移的时候:

while(q[i].l>l) del(a[l++]);
while(q[i].l<l) add(a[--l]);
while(q[i].r<r) del(a[r--]);
while(q[i].r>r) add(a[++r]);

再统计一下答案,约个分就OK了

注意:先处理第一组询问,\(l==r\)时要特判

然后?然后好像就没了,完结撒花

上代码:

#include<cstdio>
#include<cmath>
#include<iostream>
#include<algorithm>
using namespace std;
const int maxN=1e5 + 100;
struct Node
{
    int l,r,id;
}q[maxN+1];
int belong[maxN+1],block;
int a[maxN+1],n,m,l,r;
long long res[maxN+1][2],ans,cnt[maxN+1];
inline int read()
{
    int num=0,f=1;
    char ch=getchar();
    while(!isdigit(ch)) {if(ch=='-') f=-1; ch=getchar();}
    while(isdigit(ch)) num=(num<<3)+(num<<1)+(ch^48),ch=getchar();
    return num*f;
}
inline bool comp(Node a,Node b)
{
    return belong[a.l]^belong[b.l] ? belong[a.l]<belong[b.l] : belong[a.l]&1 ? a.r<b.r : a.r>b.r;
}
inline void add(int x) {ans+=2*cnt[x]+1; cnt[x]++;}
inline void del(int x) {ans-=2*cnt[x]-1; cnt[x]--;}
inline long long gcd(long long x,long long y,long long r)
{
    if(!r) return y;
    return gcd(y,r,y%r);
}
inline void work(int x)
{
    long long len=q[x].r-q[x].l+1,tmp=ans-len;
    if(!tmp) {res[q[x].id][0]=0,res[q[x].id][1]=1; return;}
    len=len*(len-1);
    long long g=gcd(tmp,len,tmp%len);
    res[q[x].id][0]=tmp/g,res[q[x].id][1]=len/g;
}
int main()
{
    n=read(),m=read(); block=n/sqrt(m*2/3);
    for(int i=1;i<=n;i++) a[i]=read(),belong[i]=(i-1)/block+1;
    for(int i=1;i<=m;i++) q[i].l=read(),q[i].r=read(),q[i].id=i;
    sort(q+1,q+m+1,comp);
    for(int i=q[1].l;i<=q[1].r;i++) add(a[i]);
    work(1);
    l=q[1].l,r=q[1].r;
    for(int i=2;i<=m;i++)
    {
        while(q[i].l>l) del(a[l++]);
        while(q[i].l<l) add(a[--l]);
        while(q[i].r<r) del(a[r--]);
        while(q[i].r>r) add(a[++r]);
        work(i);
    }
    for(int i=1;i<=m;i++) printf("%lld/%lld\n",res[i][0],res[i][1]);
    return 0;
}

转载于:https://www.cnblogs.com/cmwqf/p/10225283.html

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值