莫队算法

莫队算法

前言

当初我问ZigZagK什么是莫队算法时,他给了我一个神犇的眼神,飘了一句:“就是一个高级的暴力。”当时我彻底凌乱,后来翻了下他的Blog,发现……神犇都喜欢把话都说的很简洁……

实现

莫队算法,就是当你处理一个区间问题时,已经知道了[L,R]的答案,求[L’,R’],那么如果可以通过较快的复杂度得到[L+1,R],[L-1,R],[L,R+1],[L,R-1]的答案,就可以指针移动得到[L’,R’]。(所以确实有点暴力)

但是如果数据是[1,1]和[n,n]交替,呵呵,卡死你,所以为了优化,所以莫队用分块的思想进行了优化。

将Q个询问序列按照L分块,块内按R从小到大排序。这样的话利用复杂度是……

计算次数=L移动次数+R移动次数

L移动次数:

1.相同块中,i到i+1最多移动 n n 次,总共Q个询问,移动次数为Q* n n 。 

2.不同块中,i到i+1最多移动 n n 次,总共 n n 个块,移动次数为n。

R移动次数:

1.相同块中,总计移动最多n次,总共 n n 个块,移动次数为n n n

2.不同块中,i到i+1最多移动n次,总共 n n 个块,移动次数为n* n n

——以上证明皆由ZZK提供

所以总复杂度是 O((n+q)n) O ( ( n + q ) ∗ n )

模板

我知道我之前用树状数组写过这题,但是这道题其实是莫队裸体,所以又写了一遍……

BZOJ1878

#include<cmath>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
int n,q,S,c[50005],ha[1000005],ans[200005],sum;
struct data{
    int L,R,num,id;
    bool operator < (const data b)const{
        return num<b.num||(num==b.num&&R<b.R);
    }
}a[200005];
inline void readi(int &x){
    x=0; char ch=getchar();
    while ('0'>ch||ch>'9') ch=getchar();
    while ('0'<=ch&&ch<='9') {x=x*10+ch-'0'; ch=getchar();}
}
void _change(int x,int tem){
    if (!ha[c[x]]) sum++;
    ha[c[x]]+=tem;
    if (!ha[c[x]]) sum--;
}
int main()
{
    freopen("necklace.in","r",stdin);
    freopen("necklace0.out","w",stdout);
    readi(n); for (int i=1;i<=n;i++) readi(c[i]);
    S=sqrt(n); readi(q);
    for (int i=1;i<=q;i++){
        readi(a[i].L); readi(a[i].R);
        a[i].num=(a[i].L-1)/S+1; a[i].id=i;
    }
    sort(a+1,a+q+1); sum=0;
    memset(ha,0,sizeof(ha));
    for (int i=a[1].L;i<=a[1].R;i++) {if (!ha[c[i]]) sum++; ha[c[i]]++;} ans[a[1].id]=sum;
    for (int i=2;i<=q;i++){
        int L=a[i-1].L,R=a[i-1].R;
        while (L<a[i].L) _change(L++,-1);
        while (L>a[i].L) _change(--L,1);
        while (R<a[i].R) _change(++R,1);
        while (R>a[i].R) _change(R--,-1);
        ans[a[i].id]=sum;
    }
    for (int i=1;i<=q;i++) printf("%d\n",ans[i]);
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值