线段树分治学习笔记

前置知识 : 线段树(只需要了解其结构) 支持撤销的数据结构,如可撤销并查集,可持久化数据结构等。

线段树分治是什么

一种离线算法,用于处理假如某些信息会在某个时间段出现,求某个时刻的信息并。

常见的离线算法还有CDQ分治,考虑一下这两个算法的区别。

CDQ分治:对于每个操作,考虑其对后面询问的影响。

线段树分治: 将修改看做是一段时间区间的修改,将询问看为时间点。

啥时候用

多个操作,每一个操作的影响范围是一个区间(时间区间,询问区间)。

多个询问,询问(某个时间时,某个操作后的)答案。

如何实现

二分图 /【模板】线段树分治为例

先考虑如何判断二分图。

当一个图为二分图时,当且仅当所有边的两个点不在同一集合中。然后就可以用拓展域并查集来判断了。

假设一条边为\(x\leftrightarrow y\),那么等价于\(x\)\(y+n\)在同一集合,\(x+n\)\(y\)在同一集合。

线段树区间\([l,r]\)维护的是在\([l,r]\)出现的边,用并查集维护这个东西。

然后发现当从\([l,r]\)中退出时,要将这个区间的贡献删去,用普通并查集就维护不了了,用可撤销并查集即可。

非常好理解是不是

废话少说,上代码。

点此查看代码
#include<bits/stdc++.h>
#include<bits/extc++.h>
// using namespace __gnu_pbds;
// using namespace __gnu_cxx;
using namespace std;
#define rep(i,s,t,p) for(int i = s;i <= t; i += p)
#define drep(i,s,t,p) for(int i = s;i >= t; i -= p)
#define Infile(x) freopen(#x".in","r",stdin)
#define Outfile(x) freopen(#x".out","w",stdout)
#define Ansfile(x) freopen(#x".ans","w",stdout)
#define Errfile(x) freopen(#x".err","w",stderr)
#ifdef LOCAL
    FILE *InFile = Infile(in),*OutFile = Outfile(out);
    // FILE *ErrFile = Errfile(err);
#else
    FILE *InFile = stdin,*OutFile = stdout;
#endif
using ll=long long;using ull=unsigned long long;
using db = double;using ldb = long double;
#define eb emplace_back
const int N = 1e5 + 10;
int n,m,k;
struct EUDSU{
    vector<int> fa,siz,sta;
    inline void init(int n){
        int len = (n+1)*2+1;
        fa.resize(len),siz.resize(len);
        rep(i,1,len,1) fa[i] = i,siz[i] = 1;
    }
    inline int get_fa(int x){while(x != fa[x]) x = fa[x];return x;}
    inline void Merge(int x,int y){
        int fx = get_fa(x),fy = get_fa(y);
        if(fx == fy) return;
        if(siz[fx] < siz[fy]) swap(fx,fy),swap(x,y);
        fa[fy] = fx;
        siz[fx] += siz[fy];
        sta.eb(fy);
    }
    inline void undo(){
        int y = sta.back();sta.pop_back();
        siz[fa[y]] -= siz[y];fa[y] = y;
    }
    inline void undo(int k){while(sta.size() > k) undo();}
}D;
struct node{int x,y;}e[N<<1];
struct Segment_Tree_Divide{
    vector<int> t[N<<3];
    void upd(int k,int l,int r,int ql,int qr,int x){
        if(ql <= l && r <= qr) return t[k].eb(x),void();
        int mid = (l + r)>>1;
        if(ql <= mid) upd(k<<1,l,mid,ql,qr,x);
        if(qr > mid) upd(k<<1|1,mid+1,r,ql,qr,x);
    }
    void qry(int k,int l,int r){
        bool flag = true;
        int lasttop = D.sta.size();
        for(auto i:t[k]){
            int x = e[i].x,y = e[i].y;
            int fx = D.get_fa(x),fy = D.get_fa(y);
            if(fx == fy){
                flag = false;
                rep(i,l,r,1) cout<<"No\n";
                break;
            }
            D.Merge(e[i].x,e[i].y+n);
            D.Merge(e[i].x+n,e[i].y);
        }
        if(flag){
            if(l == r) cout<<"Yes\n";
            else{
                int mid = (l + r) >> 1;
                qry(k<<1,l,mid);qry(k<<1|1,mid+1,r);
            }
        }
        D.undo(lasttop);
    }
}T;
inline void solve(){
    cin>>n>>m>>k;
    D.init(n);
    rep(i,1,m,1){
        cin>>e[i].x>>e[i].y;
        int l,r;cin>>l>>r;l++;
        T.upd(1,1,k,l,r,i);
    }
    T.qry(1,1,k);
}
signed main(){
    cin.tie(nullptr)->sync_with_stdio(false);
    solve();
}

线段树分治大体都是这个思路。

例题

[TJOI2018] 数学计算

板子题,记录一下每个数的贡献区间即可。

点此查看代码
#include<bits/stdc++.h>
#include<bits/extc++.h>
// using namespace __gnu_pbds;
// using namespace __gnu_cxx;
using namespace std;
#define rep(i,s,t,p) for(int i = s;i <= t; i += p)
#define drep(i,s,t,p) for(int i = s;i >= t; i -= p)
#define Infile(x) freopen(#x".in","r",stdin)
#define Outfile(x) freopen(#x".out","w",stdout)
#define Ansfile(x) freopen(#x".ans","w",stdout)
#define Errfile(x) freopen(#x".err","w",stderr)
#ifdef LOCAL
    FILE *InFile = Infile(in),*OutFile = Outfile(out);
    // FILE *ErrFile = Errfile(err);
#else
    FILE *InFile = stdin,*OutFile = stdout;
#endif
using ll=long long;using ull=unsigned long long;
using db = double;using ldb = long double;
const int N = 1e5 + 10;
int bg[N],ed[N],a[N],n,mod;
struct Segment_Tree_Divide{
    struct segment_tree{
        int l,r,z,v;
        #define l(x) tree[x].l
        #define r(x) tree[x].r
        #define v(x) tree[x].v
        #define z(x) tree[x].z
    }tree[N<<2];
    inline void P(int k){v(k) = 1ll*v(k<<1)*v(k<<1|1)%mod;}
    inline void D(int k){
        if(z(k) != 1){
            int ls = k<<1,rs = k<<1|1;
            z(ls) = 1ll*z(ls)*z(k)%mod;
            z(rs) = 1ll*z(rs)*z(k)%mod;
            v(ls) = 1ll*v(ls)*z(k)%mod;
            v(rs) = 1ll*v(rs)*z(k)%mod;
            z(k) = 1;
        }
    }
    void B(int k,int l,int r){
        l(k) = l,r(k) = r,v(k) = z(k) = 1;
        if(l == r) return;
        int mid = (l + r) >> 1;
        B(k<<1,l,mid);B(k<<1|1,mid+1,r);
    }
    void U(int k,int l,int r,int v){
        if(l <= l(k) && r(k) <= r) return v(k) = 1ll*v(k)*v%mod,z(k) = 1ll*z(k)*v%mod,void();
        D(k);
        int mid = (l(k) + r(k)) >> 1;
        if(l <= mid) U(k<<1,l,r,v);
        if(r > mid) U(k<<1|1,l,r,v);
        P(k);
    }
    void Q(int k){
        if(l(k) == r(k)) return cout<<v(k)<<'\n',void();
        D(k);Q(k<<1);Q(k<<1|1);
    }
}T;
inline void solve(){
    cin>>n>>mod;
    T.B(1,1,n);
    rep(i,1,n,1){
        a[i] = 0;
        int op,x;
        cin>>op>>x;
        if(op ^ 1) ed[x] = i-1;
        else a[i] = x,bg[i] = i,ed[i] = n;
    }
    rep(i,1,n,1)
        if(a[i]) T.U(1,bg[i],ed[i],a[i]);
    T.Q(1);
}
signed main(){
    cin.tie(nullptr)->sync_with_stdio(false);
    int T;cin>>T;while(T--) solve();
}

[BJOI2014] 大融合

比较有意义的一道题,一个常见的套路。

每次询问的时候等价于将这条边断掉,然后询问\(siz_x\times siz_y\)

点此查看代码
#include<bits/stdc++.h>
#include<bits/extc++.h>
// using namespace __gnu_pbds;
// using namespace __gnu_cxx;
using namespace std;
#define rep(i,s,t,p) for(int i = s;i <= t; i += p)
#define drep(i,s,t,p) for(int i = s;i >= t; i -= p)
#define Infile(x) freopen(#x".in","r",stdin)
#define Outfile(x) freopen(#x".out","w",stdout)
#define Ansfile(x) freopen(#x".ans","w",stdout)
#define Errfile(x) freopen(#x".err","w",stderr)
#ifdef LOCAL
    FILE *InFile = Infile(in),*OutFile = Outfile(out);
    // FILE *ErrFile = Errfile(err);
#else
    FILE *InFile = stdin,*OutFile = stdout;
#endif
using ll=long long;using ull=unsigned long long;
using db = double;using ldb = long double;
const int N = 1e5 + 10;
#define vec vector
#define rz resize
#define eb emplace_back
struct DSU{
    vec<int> fa,siz,rk;
    struct undo_node{int x,y,fx,ry,sy;};vec<undo_node> sta;
    inline int get_fa(int x){while(x != fa[x]) x = fa[x];return x;}
    inline void merge(int x,int y){
        x = get_fa(x),y = get_fa(y);
        if(x == y) return;
        if(rk[x] > rk[y]) swap(x,y);
        sta.push_back({x,y,fa[x],rk[y],siz[y]});
        fa[x] = y,siz[y] += siz[x];
        if(rk[x] == rk[y]) ++rk[y];
    }
    inline int Q(int x){return siz[get_fa(x)];}
    inline void undo(){
        auto res = sta.back();sta.pop_back();
        int x = res.x,y = res.y;
        fa[x] = res.fx,rk[y] = res.ry,siz[y] = res.sy;
    }
    inline void undo(int res){while(sta.size() > res) undo();}
    inline void init(int n){
        fa.rz(n+1),siz.rz(n+1),rk.rz(n+1);
        rep(i,1,n,1) fa[i] = i,siz[i] = rk[i] = 1;
    }
}D;
#define pii pair<int,int>
#define mk make_pair
int n,q;
struct Que{char op;int x,y;}Q[N];
struct Segment_Tree_Divide{
    vec<pii> t[N<<2];
    void I(int k,int l,int r,int ql,int qr,int x,int y){
        if(ql <= l && r <= qr) return t[k].eb(mk(x,y));
        int mid = (l + r) >> 1;
        if(ql <= mid) I(k<<1,l,mid,ql,qr,x,y);
        if(qr > mid) I(k<<1|1,mid+1,r,ql,qr,x,y);
    }
    void qry(int k,int l,int r){
        int top = D.sta.size();
        for(pii i:t[k]) D.merge(i.first,i.second);
        if(l == r){
            if(Q[l].op == 'Q') 
                // cerr<<D.Q(Q[l].x)<<' '<<D.Q(Q[l].y)<<'\n',
                cout<<1ll*D.Q(Q[l].x)*D.Q(Q[l].y)<<'\n';
        }
        else{
            int mid = (l + r) >> 1;
            qry(k<<1,l,mid);qry(k<<1|1,mid+1,r);
        }
        D.undo(top);
    }
}T;
map<pii,int> mp;
inline void solve(){
    cin>>n>>q;D.init(n);
    rep(i,1,q,1){
        cin>>Q[i].op>>Q[i].x>>Q[i].y;
        if(Q[i].x > Q[i].y) swap(Q[i].x,Q[i].y);
        pii p = mk(Q[i].x,Q[i].y);
        if(Q[i].op == 'A')  mp[p] = i;
        else{
            T.I(1,1,q,mp[p],i-1,p.first,p.second);
            mp[p] = i+1;
        }
    }
    for(auto i:mp) if(i.second <= q) T.I(1,1,q,i.second,q,i.first.first,i.first.second);
    T.qry(1,1,q);
}
signed main(){
    cin.tie(nullptr)->sync_with_stdio(false);
    solve();
}

最小mex生成树

还是板子,不过变成了值域上分治。

考虑答案为\(w\),那么就等价于用\([0,w-1]\cup[w+1,\infty)\)的边可以形成一颗生成树。

然后将边权为\(w\)的边的出现区间设为\([0,w-1]\cup[w+1,\infty)\)即可。

点此查看代码
#include<bits/stdc++.h>
#include<bits/extc++.h>
// using namespace __gnu_pbds;
// using namespace __gnu_cxx;
using namespace std;
#define rep(i,s,t,p) for(int i = s;i <= t; i += p)
#define drep(i,s,t,p) for(int i = s;i >= t; i -= p)
#define Infile(x) freopen(#x".in","r",stdin)
#define Outfile(x) freopen(#x".out","w",stdout)
#define Ansfile(x) freopen(#x".ans","w",stdout)
#define Errfile(x) freopen(#x".err","w",stderr)
#ifdef LOCAL
    FILE *InFile = Infile(in),*OutFile = Outfile(out);
    // FILE *ErrFile = Errfile(err);
#else
    FILE *InFile = stdin,*OutFile = stdout;
#endif
using ll=long long;using ull=unsigned long long;
using db = double;using ldb = long double;
const int N = 1e6 + 10,V = 1e5 + 10;
#define pii pair<int,int>
#define mk make_pair
#define eb emplace_back
struct DSU{
    vector<int> fa,siz,sta;int tot;
    inline int get_fa(int x){while(x != fa[x]) x = fa[x];return x;}
    inline void init(int n){
        fa.resize(n+1);siz.resize(n+1);
        rep(i,1,n,1) fa[i] = i,siz[i] = 1;
        tot = n;
    }
    inline void merge(int x,int y){
        x = get_fa(x),y = get_fa(y);
        if(x == y) return;
        if(siz[x] > siz[y]) swap(x,y);
        fa[x] = y,siz[y] += siz[x],sta.eb(x);
        tot--;
    }
    inline void undo(){
        int x = sta.back();sta.pop_back();
        siz[fa[x]] -= siz[x];
        fa[x] = x;tot++;
    }
    inline void undo(int res){while(sta.size() > res) undo();}
}D;
struct Segment_Tree_Divide{
    vector<pii> t[V<<2];
    void I(int k,int l,int r,int ql,int qr,int x,int y){
        if(ql <= l && r <= qr) return t[k].eb(mk(x,y));
        int mid = (l + r) >> 1;
        if(ql <= mid) I(k<<1,l,mid,ql,qr,x,y);
        if(qr > mid) I(k<<1|1,mid+1,r,ql,qr,x,y);
    }
    void qry(int k,int l,int r){
        int bk = D.sta.size();
        for(auto i:t[k]) D.merge(i.first,i.second);
        if(l == r){
            // cerr<<l<<": "<<D.tot<<'\n';
            if(D.tot == 1) cout<<l<<'\n',exit(0);
            else D.undo(bk);
            return;
        }
        int mid = (l + r) >> 1;
        qry(k<<1,l,mid);qry(k<<1|1,mid+1,r);
        D.undo(bk);
    }
}T;
int n,m,u[N<<1],v[N<<1],w[N<<1],mxv;
inline void solve(){
    cin>>n>>m;D.init(n);
    rep(i,1,m,1){
        cin>>u[i]>>v[i]>>w[i];
        mxv = max(mxv,w[i] + 1);
    }
    rep(i,1,m,1){
        if(w[i]-1 >= 0) T.I(1,0,mxv,0,w[i]-1,u[i],v[i]);
        T.I(1,0,mxv,w[i]+1,mxv,u[i],v[i]);
    }
    T.qry(1,0,mxv);
}
signed main(){
    cin.tie(nullptr)->sync_with_stdio(false);
    solve();    
}

[AHOI2013] 连通图

还是板子,只不过是分治询问时间。考虑每条边不能出现的时间(即询问顺序),用个vector/set维护即可,然后直接套板子。

点此查看代码
#include<bits/stdc++.h>
#include<bits/extc++.h>
// using namespace __gnu_pbds;
// using namespace __gnu_cxx;
using namespace std;
#define rep(i,s,t,p) for(int i = s;i <= t; i += p)
#define drep(i,s,t,p) for(int i = s;i >= t; i -= p)
#define Infile(x) freopen(#x".in","r",stdin)
#define Outfile(x) freopen(#x".out","w",stdout)
#define Ansfile(x) freopen(#x".ans","w",stdout)
#define Errfile(x) freopen(#x".err","w",stderr)
#ifdef LOCAL
    FILE *InFile = Infile(in),*OutFile = Outfile(out);
    // FILE *ErrFile = Errfile(err);
#else
    FILE *InFile = stdin,*OutFile = stdout;
#endif
using ll=long long;using ull=unsigned long long;
using db = double;using ldb = long double;
const int N = 1e5 + 10,M = 2e5 + 10;
int n,m,u[M],v[M],k;
set<int> bp[M];
#define eb emplace_back
#define ins insert
struct DSU{
    vector<int> fa,siz,sta;
    int tot;
    inline void init(int n){
        fa.resize(n+1),siz.resize(n+1);
        rep(i,1,n,1) fa[i] = i,siz[i] = 1;
        tot = n;
    }
    inline int get_fa(int x){while(x != fa[x]) x = fa[x];return x;}
    inline void merge(int x,int y){
        x = get_fa(x),y = get_fa(y);
        if(x == y) return;
        if(siz[x] > siz[y]) swap(x,y);
        fa[x] = y,siz[y] += siz[x],sta.eb(x),tot--;
    }
    inline void undo(){
        int x = sta.back();sta.pop_back();
        siz[fa[x]] -= siz[x],fa[x] = x,tot++;
    }
    inline void undo(int res){while(sta.size() > res) undo();}
}D;
struct Segment_Tree_Divide{
    #define pii pair<int,int>
    #define mk make_pair
    vector<pii> t[N<<2];
    void I(int k,int l,int r,int ql,int qr,int x,int y){
        if(ql <= l && r <= qr) return t[k].eb(mk(x,y));
        int mid = (l + r) >> 1;
        if(ql <= mid) I(k<<1,l,mid,ql,qr,x,y);
        if(qr > mid) I(k<<1|1,mid+1,r,ql,qr,x,y);
    }
    void Q(int k,int l,int r){
        // cerr<<k<<' '<<l<<' '<<r<<'\n';
        int bk = D.sta.size();
        for(auto i:t[k]) D.merge(i.first,i.second);
        if(l == r){
            if(D.tot ^ 1) cout<<"Disconnected\n";
            else cout<<"Connected\n";
            D.undo(bk);
            return;
        }
        int mid = (l + r) >> 1;
        Q(k<<1,l,mid);Q(k<<1|1,mid+1,r);
        D.undo(bk);
    }
}T;
inline void solve(){
    cin>>n>>m;D.init(n);
    rep(i,1,m,1) cin>>u[i]>>v[i];
    cin>>k;
    // assert(0);
    rep(i,1,k,1){
        int c,x;cin>>c;
        rep(j,1,c,1) cin>>x,bp[x].ins(i);
    }
    rep(i,1,m,1){
        int last = 1;
        for(int j:bp[i]){
            if(j-1 < last) {last = j+1;continue;}
            T.I(1,1,k,last,j-1,u[i],v[i]);
            last = j+1;
        }
        if(last <= k) 
            T.I(1,1,k,last,k,u[i],v[i]);
    }
    T.Q(1,1,k);
}
signed main(){
    cin.tie(nullptr)->sync_with_stdio(false);
    solve();
}
例题持续更新中……(说白了就是可能会咕)
posted @ 2024-10-20 20:04  CuFeO4  阅读(34)  评论(0)    收藏  举报