Loading

离线算法入门——线段树分治

离线算法入门——线段树分治

所谓线段树分治是一种离线算法,其主要思想是把时间当做序列,在上面建线段树,每一个修改操作的影响范围在线段树上呈现,叶子节点就放询问,这样我们把所有询问离线下来,就可以在 \(f(n)\log n\) 的复杂度内完成对所有询问的回答。

线段树分治相比于其他离线算法来说,比较有优势的一点是可撤销性。即线段树分治维护的是操作影响区间,这个是 CDQ 分治及其它离线算法远远不能及的。

这些离线算法都说的非常笼统,我们直接来看一道例题。

例题

链接

我们对整个事件序列建线段树,然后对线段树的每一个节点建一个 vector ,表示在这个节点所代表时间区间里所建的边。

代码:

inline void change(int &k,int l,int r,int z,int y,int u,int v){
    if(!k) k=++tot;
    if(l==z&&r==y){
        p[k].bian.push_back(edge(u,v));return;
    }
    int mid=(l+r)>>1;
    if(y<=mid) change(p[k].l,l,mid,z,y,u,v);
    else if(z>mid) change(p[k].r,mid+1,r,z,y,u,v);
    else change(p[k].l,l,mid,z,mid,u,v),change(p[k].r,mid+1,r,mid+1,y,u,v);
}

你会发现这就是线段树的插入操作。

然后我们从根节点往下遍历,每到一个节点把里面的边合并到图上并判断是否为二分图,然后回溯的时候把所有加的边去掉。

有没有什么数据结构能够帮助我们快速的完成上面的那些操作呢?可撤销扩展域并查集就可以做到这些。

扩展域并查集用来判二分图,我们把每一个节点拆成两个节点,表示两个不同的集合,合并集合的时候注意要保证两个节点不在一个集合。如果当前的两个节点在同一集合的话,说明这不是二分图。具体看代码实现。

而可撤销并查集也并没有这样的神秘,我们只不过开一个栈把操作存进去罢了。

注意,因为要撤销操作,不能压缩路径,且为了保证复杂度正确,需要按秩合并。

代码:

#include<bits/stdc++.h>
#define dd double
#define ld long double
#define ll long long
#define uint unsigned int
#define ull unsigned long long
#define N 700010
#define M number
using namespace std;

const int INF=0x3f3f3f3f;

template<typename T> inline void read(T &x) {
    x=0; int f=1;
    char c=getchar();
    for(;!isdigit(c);c=getchar()) if(c == '-') f=-f;
    for(;isdigit(c);c=getchar()) x=x*10+c-'0';
    x*=f;
}

int n,m,k;

int fa[N],deep[N],top;
pair<int,int> sta[N];
struct bingchaji{
    inline int find(int a){
        return a==fa[a]?a:find(fa[a]);
    }
    inline bool belong_to_the_same(int a,int b){
        int faa=find(a),fab=find(b);
        return faa==fab;
    }
    inline bool merge(int a,int b){
        int faa=find(a),fab=find(b);
        if(faa==fab) return 0;
        if(deep[faa]>deep[fab]) swap(faa,fab);
        fa[faa]=fab;sta[++top]=make_pair(faa,deep[fab]);
        if(deep[faa]==deep[fab]) deep[fab]++;
        return 1;
    }
    inline void chushihua(int n){
        for(int i=1;i<=n;i++) fa[i]=i,deep[i]=0;top=0;
    }
    inline void chexiao(int k){
        for(int i=1;i<=k;i++){
            int lastdian=sta[top].first,lastdeep=sta[top].second;
            deep[fa[lastdian]]=lastdeep;fa[lastdian]=lastdian;top--;
        }
    }
};
bingchaji bcj;

struct edge{
    int from,to;
    inline edge(){}
    inline edge(int from,int to) : from(from),to(to) {}
};

struct node{
    int l,r;vector<edge> bian;
};
node p[N<<2];

int tot,root;
bool ans[N];

inline void change(int &k,int l,int r,int z,int y,int u,int v){
    if(!k) k=++tot;
    if(l==z&&r==y){
        p[k].bian.push_back(edge(u,v));return;
    }
    int mid=(l+r)>>1;
    if(y<=mid) change(p[k].l,l,mid,z,y,u,v);
    else if(z>mid) change(p[k].r,mid+1,r,z,y,u,v);
    else change(p[k].l,l,mid,z,mid,u,v),change(p[k].r,mid+1,r,mid+1,y,u,v);
}

inline void solve(int &k,int l,int r,bool op){
    if(!k) k=++tot;
    int mid=(l+r)>>1;
    int lasttop=top;
    for(int j=0;j<p[k].bian.size()&&op;j++){
        edge nowbian=p[k].bian[j];
        if(bcj.belong_to_the_same(nowbian.from,nowbian.to)){
            op=0;break;
        }
        bcj.merge(nowbian.from,nowbian.to+n);
        bcj.merge(nowbian.to,nowbian.from+n);
    }
    if(l==r){ans[l]=op;}
    else if(op){solve(p[k].l,l,mid,op);solve(p[k].r,mid+1,r,op);}
    bcj.chexiao(top-lasttop);
}

int main(){
    read(n);read(m);read(k);
    for(int i=1;i<=m;i++){
        int x,y,l,r;read(x);read(y);read(l);read(r);
        change(root,0,k,l,r,x,y);
    }
    bcj.chushihua(n<<1);
    solve(root,0,k,1);
    for(int i=0;i<=k-1;i++) if(ans[i]||ans[i+1]) printf("Yes\n");else printf("No\n");
    return 0;
}

注意:在开 O2 的情况下尽量不要用局部变量来计数,否则会 RE 的很惨。

posted @ 2021-07-07 16:01  hyl天梦  阅读(76)  评论(0编辑  收藏  举报