回滚莫队模版

回滚莫队模版

题意

给定数列,查询 \(l\)\(r\) 的众数。

思路

如果考虑暴力,我们需要一遍遍的遍历数列的数,然后求众数,但是这样效率太低了。

思考怎么优化,首先想到的线段树,但很容易发现这个众数一点都不好维护,想要全部记录,发现查找和合并的时间复杂度似乎都是 \(O(n)\) 并不好用。

容易发现这个是离线查询,可以用莫队求解,但是它不能做到删除(会退)的操作,也就是说,我们不能像莫队模版一样作奇偶优化,和左边界随便走。

所以我们参照莫队的思路,对同一块内线段的右端点来扩展右边界,而对于随机移动的左端点,我们每次移动左端点的时候,都记录一下这个起点的信息,每次做完再会退回来。至于哪里作为起点,由于一个块内所有左端点都小于这个块的右端点所以选择这个块的右端点作为起点。

code

#include <bits/stdc++.h>
#define int long long
using namespace std;
constexpr int maxn = 3e5+10;
constexpr int maxm = 700;
constexpr int mod = 10007;
constexpr int INF = 0x3f3f3f3f3f3f3f3f;
void read(int &);

int n,n_n;
int idx[maxn];// i对应的块的id
int wi[maxn]; // 离散化的值
int tmp[maxn];// 用到再说,用法。。是个tmp
int ww[maxn]; // 原值
int cnt[maxn];// 离散化的值的计数

typedef struct node
{
    int l,r,id;
    bool operator<(const node &t) const
    {
        return idx[l]==idx[t.l] ? r<t.r : idx[l]<idx[t.l];
        // 一块内按右端点排,不然按块排
    }
}node;

node qwq[maxn];// 询问
int ans[maxn];// 答案

void bf(int id) // 暴力处理小的查询
{
    int macnt=0;
    int maid=0;// 使用0而不是INF, 不然会re
    int top=0;// tmp临时存放修改的cnt
    for(int i=qwq[id].l; i<=qwq[id].r; ++i)
    {
        if(!cnt[wi[i]])
        {
            tmp[++top]=wi[i];// wa过,这里要存i离散化后的值
        }
        ++cnt[wi[i]];
        if(cnt[wi[i]]>macnt)
        {
            macnt=cnt[wi[i]];
            maid=i;
        }
        else if(cnt[wi[i]]==macnt && ww[i]<ww[maid])// 如果由有更小的数字
        {
            maid=i;
        }
    }
    ans[qwq[id].id]=ww[maid];// 存入答案,注意传进来id是块的id
    for(int i=1;i<=top;++i)
    {
        cnt[tmp[i]]=0;
    }
}

signed main()
{
    #ifndef ONLINE_JUDGE
    freopen("cjdl.in","r",stdin);
    freopen("cjdl.out","w",stdout);
    #endif // ONLINE_JUDGE

    read(n);
    n_n=sqrt(n);
    for(int i=1;i<=n;++i)
    {
        read(wi[i]);
        idx[i]=(i-1)/n_n+1;
        tmp[i]=wi[i];// 暂时存值,用于离散化
        ww[i]=wi[i];
    }
    sort(tmp+1,tmp+1+n);
    int id_w=unique(tmp+1,tmp+1+n)-tmp-1;
    for(int i=1;i<=n;++i)
    {
        read(qwq[i].l);
        read(qwq[i].r);
        qwq[i].id=i;
        wi[i]=lower_bound(tmp+1,tmp+1+id_w,wi[i])-tmp;
    }
    
    sort(qwq+1,qwq+1+n);
    int now=1;// 存储处理到了哪个块
    for(int i=1;i<=n_n+10;++i)
    {
        int L=(i-1)*n_n+1;
        int R=i*n_n;
        if(L>n)
        {
            break;
        }

        int r=R, l=R+1;// 初始设为空,先++再计算
        int maid = 0;// 下标
        int macnt=0;

        for(;now<=n && qwq[now].l<=R;++now)
        {
            if(qwq[now].r<R)// 在一个快内暴力作
            {
                bf(now);
                continue;
            }
            while(r<qwq[now].r)// 扩展右边界,注意不要吧x和r混起来
            {
                ++r;
                int x=wi[r];
                ++cnt[x];
                if(cnt[x]>macnt)
                {
                    macnt=cnt[x];
                    maid=r;
                }
                else if(cnt[x]==macnt && ww[r]<ww[maid])
                {
                    maid=r;
                }
            }

            int old_id=maid;
            int old_cnt=macnt;

            while(l>qwq[now].l)// 扩展右边界
            {
                --l;
                int x=wi[l];
                ++cnt[x];
                if(cnt[x]>macnt)
                {
                    macnt=cnt[x];
                    maid=l;
                }
                else if(cnt[x]==macnt && ww[l]<ww[maid])
                {
                    maid=l;
                }
            }
            
            ans[qwq[now].id]=ww[maid];
            
            while(l<=R)// 回滚
            {
                --cnt[wi[l++]];
            }
            macnt=old_cnt;// 恢复l 在R+1的情况
            maid=old_id;
        }
        memset(cnt,0,sizeof cnt);// 清空
    }

    for(int i=1;i<=n;++i)
    {
        printf("%lld\n",ans[i]);
    }

    return 0;
}

void read(int &x)
{
    x=0;
    int f=1;
    signed c=getchar();
    while(!isdigit(c))
    {
        if(c=='-')
        {
            f=-1;
        }
        c=getchar();
    }
    while(isdigit(c))
    {
        x=(x<<1)+(x<<3)+(c^48);
        c=getchar();
    }
    x*=f;
}
posted @ 2025-11-22 16:41  玖玮  阅读(1)  评论(0)    收藏  举报