再探莫队算法

每日打卡

对于牛客暑期多校训练营第一场的J题,就是一个典型的莫队算法(但是被卡了),我也不知道这是什么玄学原理。

题目链接:https://www.nowcoder.com/acm/contest/139/J

题意:给n个数,q组询问,每组询问有l和r两个数,求a[1],a[2],...,a[l],a[r],...,a[n]中间有多少个不同的数。

题解:裸的莫队。

#include <bits/stdc++.h>
using namespace std;
 
#define INIT(x) memset(x,0,sizeof(x))
const int maxn = 100005;
 
inline int read()
{
    int x=0,f=1;char ch=getchar();
    while(ch<'0'||ch>'9') {if(ch=='-') f=-1;ch=getchar();}
    while(ch>='0'&&ch<='9') {x=x*10+ch-'0';ch=getchar();}
    return x*f;
}
 
struct node{
    int l,r,id;
}q[maxn];
 
int n,m,a[maxn],ans[maxn],num[maxn],tot;
int block;
 
bool cmp(const node &a,const node &b)
{
    if(a.l/block!=b.l/block)
        return a.l<b.l;
    return a.r<b.r;
}
 
inline void add(int x)
{
    num[a[x]]++;
    if(num[a[x]]==1)
        tot++;
}
 
inline void del(int x)
{
    num[a[x]]--;
    if(num[a[x]]==0)
        tot--;
}
 
void init()
{
    INIT(num);
    INIT(a);
}
 
int main()
{
    while(~scanf("%d%d",&n,&m))
    {
        init();
        for(int i=0;i<n;i++)
            a[i] = read();
        block = sqrt(maxn);
        for(int i=0;i<m;i++)
        {
            q[i].l = read()-1;
            q[i].r = read()-1;
            q[i].id = i;
        }
        sort(q,q+m,cmp);
        int l=0,r=n-1; //注意这里,因为题目相当于挖去了中间区间,求其他区间不同的数个数,所以刚开始l和r的位置在一头一尾,并且它们(头尾)永远在最终要求的区间里,所以要先预处理
        num[a[0]]++;
        num[a[n-1]]++;
        if(a[0]==a[n-1])
            tot = 1;
        else
            tot = 2;
        for(int i=0;i<m;i++)
        {
            while(l<q[i].l)  //如果指针在区间左边,因为是求的外区间,所以就把这个数填上
                add(++l);
            while(l>q[i].l)
                del(l--);
            while(r<q[i].r)
                del(r++);
            while(r>q[i].r)
                add(--r);
            ans[q[i].id] = tot;
        }
        for(int i=0;i<m;i++)
            printf("%d\n",ans[i]);
    }
    return 0;
}

   


例题2:https://www.luogu.org/problemnew/show/P1972

题意:和上题相反,此题给l和r区间,求a[l]到a[r]中有多少个不同的数。

题解:首先,弱数据可以用map过掉。但我们还是考虑用莫队来做这道题。由于题意的变化,所以我们需要变化初始指针的位置,让它均指向开头。

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<vector>
#include<queue>
using namespace std;

#define INIT(x) memset(x,0,sizeof(x))

const int inf = 0x3f3f3f3f;
const int maxn = 1000005;

int a[maxn],n,m,block,ans[maxn],num[maxn],tot;

struct node{
    int l,r,id;
    bool operator<(const node&b)const{
        if(l/block!=b.l/block)
            return l<b.l;
        return r<b.r;
    }
}q[maxn];

inline void read(int &x)
{
    int f=1;x=0;char s=getchar();
    while(s<'0'||s>'9'){if(s=='-')f=-1;s=getchar();}
    while(s>='0'&&s<='9'){x=x*10+s-'0';s=getchar();}
    x*=f;
}

inline void print(int x)
{
    if(x<0)
    {
        putchar('-');
        x=-x;
    }
    if(x>9)
        print(x/10);
    putchar(x%10+'0');
}

inline void add(int x)
{
    num[a[x]]++;
    if(num[a[x]]==1)
        tot++;
}

inline void del(int x)
{
    num[a[x]]--;
    if(num[a[x]]==0)
        tot--;	
}

int main()
{
    read(n);
    block = sqrt(n);
    for(int i=1;i<=n;i++)
        read(a[i]);
    read(m);
    for(int i=0;i<m;i++)
    {
        read(q[i].l);
        read(q[i].r);
        q[i].id = i;
    }
    sort(q,q+m);
    int l=0,r=0; //由于这次要求是在区间里面,所以刚开始两指针全都指向0,再依次向后移动
    tot = 0;
    for(int i=0;i<m;i++)
    {
        while(l<q[i].l) //如果指针在区间外面,则删掉区间外面的元素对应的num值,再把指针往里面挪
            del(l++);
        while(l>q[i].l)
            add(--l);
        while(r<q[i].r)
            add(++r);
        while(r>q[i].r)
            del(r--);
        ans[q[i].id] = tot;
    }
    for(int i=0;i<m;i++)
        cout<<ans[i]<<endl;
    return 0;
}


例3:Codeforces Round #340 (Div. 2) E. XOR and Favorite Number

题目链接:http://codeforces.com/contest/617/problem/E

扫描二维码关注公众号,回复: 2531275 查看本文章

题意:对于询问区间L和R,求有多少对l和r满足 a[l] xor a[l+1] xor.......xor a[r] = k。

题解:

       首先可以求一下前缀和,pre[i]=a[1]^a[2]...^a[i];(pre[0]=0)

       如果一个区间 a[l] xor a[l+1] xor.......xor a[r] = k , 那么就有pre[l-1]^pre[r] = a[l] xor a[l+1] xor.......xor a[r], 所求的问题便转化成了求一对l和r,使得pre[l-1]^pre[r]=k。我们依旧可以用莫队算法来解决这个问题。

        当然了,本题有几个注意点:

  •  num数组记录的是pre[i]^k的结果的个数,即num[pre[l-1]^k]的个数等于num[pre[r]](若pre[l-1]^pre[r]=k) 的个数;
  •  num[0] = 1, 这是因为如果存在一个区间[0,0,0,,,k],它们的异或和为k,出现这种情况的话就会存在一种解法,所以num[0]=1;
  •  开始指针l指向1,r指向1,是因为如果l指向0的话,则会出现l-1指向-1发生错误
#include<bits/stdc++.h>
using namespace std;

const int maxn = 1<<20;
typedef long long ll;

ll n,m,k,block,num[maxn],tot,ans[maxn];
ll a[maxn];

struct node{
    int l,r,id;
    bool operator<(const node&b)const{
        if(l/block!=b.l/block)
            return l<b.l;
        return r<b.r;
    }
}q[maxn];

int main()
{
    scanf("%lld%lld%lld",&n,&m,&k);
    memset(num,0,sizeof(num));
    for(int i=1;i<=n;i++)
    {
        scanf("%lld",&a[i]);
        a[i] ^= a[i-1];
    }
    block = sqrt(n);
    for(int i=0;i<m;i++)
    {
        scanf("%d%d",&q[i].l,&q[i].r);
        q[i].id = i;
    }
    sort(q,q+m);
    tot = 0;
    ll l=1,r=0;
    num[0] = 1;
    for(int i=0;i<m;i++)
    {
        while(l<q[i].l){
            num[a[l-1]]--;
            tot -= num[a[l-1]^k];
            l++;
        }
        while(l>q[i].l){
            l--;
            tot += num[a[l-1]^k];
            num[a[l-1]]++;
        }
        while(r<q[i].r){
            r++;
            tot += num[a[r]^k];
            num[a[r]]++;
        }
        while(r>q[i].r){
            num[a[r]]--;
            tot -= num[a[r]^k];
            r--;
        }
        ans[q[i].id] = tot;
    }
    for(int i=0;i<m;i++)
        cout<<ans[i]<<endl;
    return 0;
}

猜你喜欢

转载自blog.csdn.net/zxwsbg/article/details/81176057