首页 > 其他分享 >线段树分治学习笔记

线段树分治学习笔记

时间:2024-10-20 20:00:30浏览次数:5  
标签:int siz 线段 分治 笔记 fa long using define

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

线段树分治是什么

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

常见的离线算法还有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();    
}

例题持续更新中……(说白了就是可能会咕)

标签:int,siz,线段,分治,笔记,fa,long,using,define
From: https://www.cnblogs.com/hzoi-Cu/p/18487766

相关文章

  • spring笔记
    @Slf4j@RestController@Validated1、Circularviewpath[register]:woulddispatchbacktothecurrenthandlerURL[/register]again.(循环视图路径)把@Controller改成@RestController (相当于@Controller和@ResponseBody的组合)2、@Slf4j注解使用,方便调试log.info......
  • C语言深入理解指针笔记(3)
    1.字符指针变量 我们已经了解的指针变量类型有:整形指针变量:int*pint:存放的是整型变量的地址浮点型指针变量:float*pf:存放的是浮点型变量的地址类比可知:char*pc:字符型指针变量:存放的是字符型变量的地址,指向字符型的数据 首先,字符型指针变量的使用有两种方法:......
  • 插头 dp / 轮廓线 dp / 连通性 dp 做题笔记
    牢游看见我正在做插头dp,于是给我了一个Claris的连通性dp的pdf。好了,现在又有可以软性颓废的事可干了。好多题目在其他平台都找不到了,这时候我们becoder的优越性就体现出来了!(这就是到处搬题的好处)所以大部分题目链接都会放becoder的链接。什么?你不知道becoder或者没......
  • 【报告】务虚笔记
    务虚笔记同学们大家好,接下来由我向大家推荐史铁生的《务虚笔记》我的报告分为四部分。书籍简介首先是书籍简介。务虚笔记是史铁生先生的首部长篇小说,于1996年发表在《收获》杂志上。它的行文优美、凝练,情感真挚、厚重,语言平实易读,虽然理解它的内容会让第一次读此书的读者......
  • Rest-Assured 学习笔记
    Rest-Assured学习笔记Rest-Assured学习笔记安装Rest-Assured<dependencies><dependency><groupId>io.rest-assured</groupId><artifactId>rest-assured</artifactId><version>4.3.0</version>......
  • 锐捷策略路由笔记
    概念:内部流量的负载分担:识别到流量后强制更改下一跳冗余备份:如果一侧链路断开会走另一条链路配置接口地址RSR1:intg0/2noswitchipadd192.168.100.124RSR2:intg0/0noswitchipadd192.168.100.224intg0/1noswitchipadd1.1.1.124intg0/2noswitc......
  • Linux学习笔记(复习版day008)
    1.僵尸进程僵尸进程(ZombieProcess)是指那些已经终止(即完成执行)的进程,但其父进程尚未读取其退出状态信息的进程。简单来说,僵尸进程的生命周期已经结束,但它的进程描述符仍然存在于系统中,以便父进程能够获取其退出状态。处理:1.top命令查询是否有僵尸进程,此处1zombie表示有一个......
  • C语言笔记21 字符串
    字符数组charword[]={'H','e','l','l','o','!'};word[0]Hword[1]eword[2]lword[3]lword[4]oword[5]!这不是C语言的字符串,只是字符数组,不能用字符串的方式做计算字符串charword[]={'H','e','l','l&......
  • 《深入理解Java虚拟机》读后笔记-垃圾收集器
    优点:与其他收集器的单线程相比简单而高效,对于内存资源受限的环境,它是所有收集器里额外内存消耗最小的。对于单核处理器或处理器核心数较少的环境来说,Serial收集器由于没有线程交互的开销,专心做垃圾收集自然可以获得最高的单线程收集效率应用场景:Serial收集器对于运行......
  • 大数据之路读书笔记(六)
    前言    上一章主要研究的是无线客户端日志采集中与浏览器端日志采集相对应的技术方案,现在开始要进行无线客户端日志采集特有技术的学习。特殊场景    页面事件和控件点击及其他事件都是一个行为产生一条日志,如果处理普通业务场景是足够的,但是一但业务场景......