学会优雅地打暴力——莫队学习笔记

莫队学习笔记


莫队是啥?(怎么每篇都在问这个问题?)

莫队,是一种与分块齐名的\(O(N\log{N})\)的暴力美学算法,常用来骗分和伪装正解

如何实现莫队?

我们来一道经典题看下莫队的表现吧:

\[\text{[SDOI2009]HH的项链}\]

题目描述

HH有一串由各种漂亮的贝壳组成的项链。HH 相信不同的贝壳会带来好运,所以每次散步完后,他都会随意取出一段贝壳,思考它们所表达的含义。HH 不断地收集新的贝壳,因此,他的项链变得越来越长。有一天,他突然提出了一个问题:某一段贝壳中,包含了多少种不同的贝壳?这个问题很难回答……因为项链实在是太长了。于是,他只好求助睿智的你,来解决这个问题。

大概就是给出一个序列,每次询问区间中元素种类数...

教练我会暴力!

我打了一个暴力程序,看看就好

#include<bits/stdc++.h>
using namespace std;

int n,m,l,r,ans;
int a[500005];
bool cnt[1000005];

template<class T>inline void read(T &res)
{
    T flag=1;char c;
    while((c=getchar())<'0'||c>'9')if(c=='-')flag=-1;res=c-'0';
    while((c=getchar())>='0'&&c<='9')res=(res<<1)+(res<<3)+c-'0';res*=flag;
}

int main()
{
    read(n);
    for(register int i=1;i<=n;++i) read(a[i]);
    read(m);
    for(register int i=1;i<=m;++i)
    {
        read(l);read(r);ans=0;
        for(register int j=l;j<=r;++j) cnt[a[j]]=1;
        for(register int j=0;j<=1000005;++j)
        {
            if(cnt[j]) ++ans;
            cnt[j]=0;
        }
        printf("%d\n",ans);
    }
    return 0;
}

20分,开不开心?

o_md1.png

我们需要优化!

很快地,我们想到了一个优化:当遍历到一个数的时候,如果这个数还没出现过,ans+1,去除一个数的时候,如果再也没有这个数了,ans-1!

并没有什么用……

继续优化!

考虑到每个区间可能会有重叠,我们不立即枚举区间了!我们通过指针调整区间位置来统计答案!

o_md2.gif

如上图,通过指针的移动,统计指到的每一个数,便可以得出答案

于是我们改出了以下的代码:

#include<bits/stdc++.h>
using namespace std;

int n,m,ql,qr,a[500005];
int l=1,r=0,now=0,cnt[1000005],aa[500005];

inline void add(int pos)
{
    if(!cnt[aa[pos]]) ++now;
    ++cnt[aa[pos]];
}

inline void del(int pos)
{
    --cnt[aa[pos]];
    if(!cnt[aa[pos]]) --now;
}

template<class T>inline void read(T &res)
{
    T flag=1;char c;
    while((c=getchar())<'0'||c>'9')if(c=='-')flag=-1;res=c-'0';
    while((c=getchar())>='0'&&c<='9')res=(res<<1)+(res<<3)+c-'0';res*=flag;
}

inline void work()
{
    for(register int i=1;i<=m;++i)
    {
        read(ql);read(qr);
        while(l<ql) del(l++);
        while(l>ql) add(--l);
        while(r<qr) add(++r);
        while(r>qr) del(r--);
        printf("%d\n",now);
    }
}

int main()
{
    read(n);
    for(register int i=1;i<=n;++i) read(aa[i]);
    read(m);
    work();
    return 0;
}

这个优化力度远远大于上一个优化,因此效果非常显著

(o_QQ%E6%88%AA%E5%9B%BE20190724081920.png

但是,仍然T了5个点,因此我们还要继续优化!

继续优化

  1. 奇偶排序

当有多个查询区间时,我们将大小为n的序列分为\(1\)~\(\sqrt{n}\)个块,从\(1\)\(\sqrt{n}\)编号,然后根据这个对查询区间进行排序

把查询区间按照左端点所在块的序号排个序,如果左端点所在块相同,再按右端点排序

int cmp(query a, query b)
{
    return belong[a.l]==belong[b.l]?a.r<b.r:belong[a.l]<belong[b.l];
}

这样排序之后优化力度已经很大了,但还不够!

对于左端点在同一奇数块的区间,右端点按升序排列,反之降序(即奇偶排序)

int cmp(query a, query 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);
}
  1. 指针压缩

这里主要是优化常数,将上面的函数和判断压缩成下面这个样子

while(l<ql) now-=!--cnt[aa[l++]];
while(l>ql) now+=!cnt[aa[--l]]++;
while(r<qr) now+=!cnt[aa[++r]]++;
while(r>qr) now-=!--cnt[aa[r--]];

然后就A了

o_md5.png

对比:

o_md4.png

完整代码:

#include<bits/stdc++.h>
#define N 1000005
#define ql q[i].l
#define qr q[i].r
using namespace std;

int n,m,l=1,r=0;
int belong[N],a[N],cnt[N],now,ans[N];

struct query
{
    int l,r,id;
}q[1000005];

bool cmp(query a,query 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);
}

template<class T>inline void read(T &res)
{
    T flag=1;char c;
    while((c=getchar())<'0'||c>'9')if(c=='-')flag=-1;res=c-'0';
    while((c=getchar())>='0'&&c<='9')res=(res<<1)+(res<<3)+c-'0';res*=flag;
}

template<class T>inline void write(T x)
{
    if(x<0)putchar('-'),x=-x;
    if(x>=10)write(x/10);
    putchar('0'+x%10);
}

int main()
{
    read(n);
    int size=sqrt(n);
    int bnum=ceil((double)n/size);
    for(register int i=1;i<=bnum;++i)
        for(register int j=(i-1)*size;j<=i*size;++j)
            belong[j]=i;
    for(register int i=1;i<=n;++i) read(a[i]);
    read(m);
    for(register int i=1;i<=m;++i)
    {
        read(q[i].l);
        read(q[i].r);
        q[i].id=i;
    }
    sort(q+1,q+m+1,cmp);
    for(register int i=1;i<=m;++i)
    {
        while(l<ql) now-=!--cnt[a[l++]];
        while(l>ql) now+=!cnt[a[--l]]++;
        while(r<qr) now+=!cnt[a[++r]]++;
        while(r>qr) now-=!--cnt[a[r--]];
        ans[q[i].id]=now;
    }
    for(register int i=1;i<=m;++i)
        write(ans[i]),putchar('\n');
    return 0;
}

https://home.cnblogs.com/u/tqr06/

https://www.cnblogs.com/tqr06/p/10400144.html

转载于:https://www.cnblogs.com/tqr06/p/11234683.html

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值