bzoj4358.permu 回滚莫队+可删除并查集(简易)|详细解析

今天学了回滚莫队,为什么会有回滚莫队呢?

比如普通莫队求max时就很不好撤销,那我们就换个思路,干脆不删了,通过调整枚举顺序进行暴力加点

回滚莫队的思想主要是这段代码:

bool cmp(const query& a,const query& b){
    if(bel[a.l]==bel[b.l]) return a.r<b.r;
    else return bel[a.l]<bel[b.l]; 
}

 

首先按左端点所属的块号排序,然后块号相同的按右端点排序

移动的时候:

1.假设左右端点在同一个块内,因为块的大小不超过sqrt(n),所以直接暴力扫

2.假设此次询问的左端点和上次询问的左端点在同一个块内,那么令:

l=R[bel[q[i].l]]+1;
r=R[bel[q[i].l]]; 

然后开始跳到询问区间即可

3.假设此次询问的左端点和上一次询问的左端点在同一个块内,那么分别考量l和r

r一定是递增的(想想为什么?我们排序的时候保证了),我们就可以直接更新

但是l就不一定了,是乱序的

虽然是乱序,但也可以暴力跳,因为已经保证了在同一个块内了,暴力跳的话也不会超过sqrt(n)次

我们再设一个临时变量__l=l,跳的时候让__l去跳,而不是让l去跳

这样就保证了到下一次询问时,又能从l开始跳了。

所以大概的移动可能是长这样的:

 

 (呜呜没有数位板)

时间复杂度:r的更新最多只有n次,而l也最多只会跳sqrt(n)次,总的还是n*sqrt(n)

话说回本题上来:

要求最长的连续值域

维护一个并查集,更新时判一下a[pos]-1,和a[pos]+1当前有没有vis到,有的话就塞进并查集里

但是要注意,__l回滚的时候,不仅要撤销vis的标记

还要撤销对并查集的改动,这个可以开个stack记录一下

 具体的,如何维护并查集的撤销呢?

在__l往左边跳时,把此时的栈顶当做相对栈底,记作bottom

merge(x,y)的时候把sz小的x压进栈里

撤销时只要:

            while(s.size()>bottom){
              int u=s.top();s.pop();
              siz[fa[u]]-=siz[u];
              fa[u]=u;
             // siz[u]=1;
            }

很重要的一点是,不能写路径压缩!错误性自己模拟一下就知道了:D

另,这种写法适用范围不是很广,甚至不是板子,因为只适用于小数据,比如这题的sqrt(1e5),不过在这里也够用了

复制代码
#include<bits/stdc++.h>
#define fastio ios_base::sync_with_stdio(false);cin.tie(0);cout.tie(0)
using namespace std;
typedef long long ll;
const int maxn=1e5+5;
int out[maxn],dp[maxn],use[maxn];
int n,Q,sz,tot,L[maxn],R[maxn],bel[maxn],a[maxn],vis[maxn];
struct query{
    int l,r,id;
}q[maxn];
stack<int>s;

    int fa[maxn],siz[maxn];
    void init(){
        for(int i=1;i<=n;i++) fa[i]=i,siz[i]=1;
    }
    int find(int x){
        if(fa[x]!=x) return find(fa[x]);
        else return fa[x];
    }
    int ask(int u){
        return siz[find(u)];
    }
    void merge(int x,int y){
        int fx=find(x),fy=find(y);
        if(siz[fx]>siz[fy]) swap(fx,fy); 
        siz[fy]+=siz[fx];
        fa[fx]=fy;
        s.push(fx);
    }
bool cmp(const query& a,const query& b){
    if(bel[a.l]==bel[b.l]) return a.r<b.r;
    else return bel[a.l]<bel[b.l]; 
}
void build(){
    sz=sqrt(n);
    tot=n/sz;
    for(int i=1;i<=tot;i++){
        L[i]=(i-1)*sz+1;
        R[i]=i*sz;
    }
    if(R[tot]<n) {
        tot++;
        L[tot]=R[tot-1]+1;
        R[tot]=n;
    }
    for(int i=1;i<=tot;i++)
     for(int j=L[i];j<=R[i];j++) 
      bel[j]=i;
}
void add(int pos,int& ans){
    vis[a[pos]]=1;
    if(vis[a[pos]-1]){
        merge(a[pos]-1,a[pos]);
    }
    if(vis[a[pos]+1]){
        merge(a[pos],a[pos]+1);
    }
    ans=max(ans,ask(a[pos]));
}
int main(){
    //freopen("lys.in","r",stdin);
    fastio;
    cin>>n>>Q;
    
    build();
    init();
    for(int i=1;i<=n;i++) cin>>a[i];
    for(int i=1;i<=Q;i++){
        cin>>q[i].l>>q[i].r;q[i].id=i;
    }
    sort(q+1,q+Q+1,cmp);
    int l=1,r=0;
    int last_block=0;
    int ans=0;
    for(int i=1;i<=Q;i++){
        //同一块直接暴力扫~ 
        if(bel[q[i].l]==bel[q[i].r]){
            int tmp=0;
            for(int j=q[i].l;j<=q[i].r;j++){
                use[++tmp]=a[j];
            }
            sort(use+1,use+tmp+1);
            use[0]=dp[0]=0;
            out[q[i].id]=dp[1]=1;
            for(int j=2;j<=tmp;j++){
                if(use[j]==use[j-1]+1) dp[j]=dp[j-1]+1;
                else dp[j]=1;
                out[q[i].id]=max(out[q[i].id],dp[j]);
            }
            continue;
        }
        // 如果进入了新块 
        if(bel[q[i].l]!=last_block){ 
            // 觉得这边直接memset也可——但是可能会t掉
            l=R[bel[q[i].l]]+1;
            r=R[bel[q[i].l]]; 
            init();
            memset(vis,0,sizeof(vis));
            last_block=bel[q[i].l];
            ans=1;
        }
            int __l=l;// 这里l其实是固定的,可以发现只有在块间移动时才会变化
            // 回滚其实都是通过__l来实现 
            while(r<q[i].r) add(++r,ans);
            int tmp=ans;
            int bottom=s.size();
            while(__l>q[i].l) {
             // do something
                add(--__l,tmp); 
            }
            out[q[i].id]=tmp;
            while(s.size()>bottom){
              int u=s.top();s.pop();
              siz[fa[u]]-=siz[u];
              fa[u]=u;
             // siz[u]=1;
            }
            while(__l<l) vis[a[__l++]]=0;
    }
    for(int i=1;i<=Q;i++) cout<<out[i]<<endl;
}
复制代码

 

posted @   liyishui  阅读(55)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· C#/.NET/.NET Core优秀项目和框架2025年2月简报
· Manus爆火,是硬核还是营销?
· 终于写完轮子一部分:tcp代理 了,记录一下
· 【杭电多校比赛记录】2025“钉耙编程”中国大学生算法设计春季联赛(1)
点击右上角即可分享
微信分享提示