P13690 [CEOI 2025] boardgames

P13690 [CEOI 2025] boardgames

描述

给定一张无向图,将点按编号划分成若干区间的导出子图,要求满足所有导出子图联通,求最小联通数。

思路

主要来自于 jiangly。

存在比较暴力的做法,依次找到极小的出现断开位置的块,进行递归处理,显然会被做到 \(n^2\)

对于这类似的划分,可以尝试启发式分裂。

如果我们可以在 \(min(a,b)\) 的时间里,将连通块划分成 \(a\)\(b\) 两个不联通的,进行处理,那确实是可以做到 \(\log\) 的,但是该问题,对于较大的连通块不方便进行删边操作。

可能可以使用LCT进行无脑处理,但是太不优美,这里存在比较优秀的做法。

对于分治的区间 \([l,r]\),选取中点 \(mid\),每次只保留 \(mid\) 的所在的区间,这样的分治只包含 \(\log\) 层,因为每次递归下去的长度是 \(\leq \frac{len}{2}\) 的。

但是还是存在删边的问题,我们一般使用可持久化并查集来做带删边的联通性问题,但是在该问题下,他左右两边都要进行撤销操作,我们发现从中间进行加边的时候,左右交错太密集和太稀疏都不是很对。

事实上可以使用二进制分组,我们可以发现这样做单层是 \(log\) 级别的,看上去整体是一个 \(log^2 n\),但是 jiangly 可以分析到整体 \(n\log n\),实现也比较快,加上并查集是一个 \(n log^2 n\) 的复杂度,可以通过。

注意到:应当按照边进行分治,每次添加点是 \(O(deg)\) 级别的,而添边是 \(O(1)\) 的,这样才能确保复杂度。

实现:

#include<bits/stdc++.h>
using namespace std;
vector<int> partition_players(int n, int m,vector<int> X,vector<int> Y);
const int maxm=2e5+5;
const int maxn=1e5+5;
struct edge{
    int x,y,id;
}e[(maxm+maxn)<<1];
set< pair<int,int> > st;
int vc[maxm+maxn];
int fa[maxn],siz[maxn];
int be[maxn],en[maxn];
inline int find(int x){
    return x==fa[x]?x:find(fa[x]);
}
struct node{
    int id,val,op;
};
stack<node> st1,st2;
struct seq{
    int l,r,op;
};
stack<seq> sta;
int tt;
int nl,nr;
inline void add(int l,int r){
    ++tt;
    sta.push({l,r,tt});
    for(int i=l;i<=r;i++){
        st2.push({e[i].id,vc[e[i].id],tt});
        vc[e[i].id]++;
        if(vc[e[i].id]==2){
            int x=find(e[i].x),y=find(e[i].y);
            if(x!=y){
                if(siz[x]<siz[y]){
                    st1.push({x,fa[x],tt});
                    fa[x]=y;
                    siz[y]+=siz[x];
                }
                else{
                    st1.push({y,fa[y],tt});
                    fa[y]=x;
                    siz[x]+=siz[y];
                }
            }
        }
    }
}
inline void remove(int o){
    while(st1.size() && st1.top().op==o){
        siz[fa[st1.top().id]]-=siz[st1.top().id];
        fa[st1.top().id]=st1.top().val;
        st1.pop();
    }
    while(st2.size() && st2.top().op==o){
        vc[st2.top().id]=st2.top().val;
        st2.pop();
    }
}
int tw[(maxm+maxn)*2];
inline int get(int b){
    if(tw[b]) return tw[b];
    int ret=1;
    while(ret*2<=b) ret*=2;
    return tw[b]=ret;
}
inline void add_block(int bl,int br){
    while(bl || br){
        if(bl>br){
            int re=get(bl);
            add(nl-re,nl-1);
            nl-=re;
            bl-=re;
        }
        else{
            int re=get(br);
            add(nr+1,nr+re);
            nr+=re;
            br-=re;
        }
    }
}
inline void solve(int l,int r,int bl,int br){
    if(l==r){
        st.insert({l,r});
        return ;
    }
    int mid=(l+r)>>1;
    for(int i=l;i<=r;i++){
        fa[i]=i,siz[i]=1;
    }
    for(int i=bl;i<=br;i++) vc[e[i].id]=0;
    while(!sta.empty()) sta.pop();
    while(!st1.empty()) st1.pop();
    while(!st2.empty()) st2.pop();
    vector< pair<int,int> > sl;
    nl=be[mid],nr=en[mid];
    add(nl,nr);
    add_block(be[mid]-be[l],en[r]-en[mid]);
    int L=l,R=r;
    while(L!=R){
        bool flag=0;
        for(int i=1;i<=max(mid-L,R-mid);i++){
            if(L+i<=mid){
                if(find(L+i)!=find(L+i-1)){
                    sl.push_back({L,L+i-1});
                    while(1){
                        seq tmp=sta.top();
                        sta.pop();
                        if(tmp.r<be[mid]){
                            nl=tmp.r+1;
                            remove(tmp.op);
                            if(tmp.r+1>=be[L+i]) break;
                        }
                        else{
                            nr=tmp.l-1;
                            remove(tmp.op);
                        }
                    }
                    L+=i;
                    int bl=nl-be[L],br=en[R]-nr;
                    add_block(bl,br);
                    flag=1;
                    break;
                }
            }
            if(R-i>=mid){
                if(find(R-i)!=find(R-i+1)){
                    sl.push_back({R-i+1,R});
                    while(1){
                        seq tmp=sta.top();
                        sta.pop();
                        if(tmp.r<be[mid]){
                            nl=tmp.r+1;
                            remove(tmp.op);
                        }
                        else{
                            nr=tmp.l-1;
                            remove(tmp.op);
                            if(tmp.l-1<=en[R-i]) break;
                        }
                    }
                    R-=i;
                    int bl=nl-be[L],br=en[R]-nr;
                    add_block(bl,br);
                    flag=1;
                    break;
                }
            }
        }
        if(!flag) break;
    }
    st.insert({L,R});
    for(auto x:sl){
        solve(x.first,x.second,be[x.first],en[x.second]);
    }
}
inline bool cmp(edge x,edge y){
    if(x.x!=y.x) return x.x<y.x;
    return x.y<y.y;
}
int tot;
vector<int> partition_players(int n, int m,vector<int> X,vector<int> Y){
    for(int i=0;i<m;i++){
        e[++tot]={X[i]+1,Y[i]+1,i+1};
        e[++tot]={Y[i]+1,X[i]+1,i+1};
    }
    for(int i=1;i<=n;i++){
        int id=tot/2+1;
        e[++tot]={i,i,id};
        e[++tot]={i,i,id};
    }
    sort(e+1,e+tot+1,cmp);
    for(int i=1;i<=tot;i++){
        if(!be[e[i].x]) be[e[i].x]=i;
        en[e[i].x]=i;
    }
    solve(1,n,1,tot);
    vector<int> ans;
    for(auto x:st){
        ans.push_back(x.second-x.first+1);
    }
    return ans;
}
/*
int main(){
    int n,m;
    scanf("%d%d",&n,&m);
    vector<int> X,Y;
    X.resize(m),Y.resize(m);
    for(int i=0;i<m;i++) scanf("%d%d",&X[i],&Y[i]);
    vector<int> ans=partition_players(n,m,X,Y);
    printf("%d\n",(int)ans.size());
    for(auto x:ans) printf("%d ",x);
    return 0;
}
*/
posted @ 2025-10-09 19:52  脚踩ag手捏an  阅读(8)  评论(1)    收藏  举报