zoukankan      html  css  js  c++  java
  • 线段树分治

    引子

    摘抄自二分图 /【模板】线段树分治题解

    对于这类的问题

    • 有一些操作,每个操作只在 (l sim r) 时间段内有效。
    • 有一些询问,每个询问某一个时间点所有操作的贡献。

    对于这样的询问,可以对时间建一棵线段树,对于每个操作,相当于在线段树上做区间操作

    遍历整颗线段树,到达每个节点时执行相应的操作,然后继续向下递归,到达叶子节点时统计贡献,回溯时撤销操作即可。

    这样的思想被称为线段树分治,可以在低时间复杂度内解决一类在线算法不优秀的问题。

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

    如何判断是否为二分图:

    二分图:把图中所有点分成两个集合,使得所有边的两个顶点不在一个集合中。也就是一个不存在奇环的图

    常用的方法是染色:如果是二分图,那么一条边的两个顶点一定是不同集合的,也就是颜色不同

    还有一种方法就是值域并查集

    • 扩展域并查集:对于一个节点 (i) ,我们将其拆分为两个节点。一个属于集合 (S) ,另一个属于集合 (T) 。那么一条边所连接的两个节点就必须在不同的集合中。一个点在 (S) 中和在 (T) 的两个点属于一个集合,那么这张图就不是二分图。

    对于时间端:

    我们按照上面,对时间段建立线段树,每个节点开一个 vector,存下这个时间端内存在的边,遍历时,从根节点出发,每到一个节点,将挂在该节点上的所有边合并,然后递归处理左儿子和右儿子。如果发现有某条边合并会出现奇环,那么当前线段树节点所对应的时间区间都不会形成二分图。

    当到达叶子节点时,如果合并了所有挂在当前节点上的边,依旧满足二分图的性质,那这个时刻就可以形成二分图。

    回溯的时候,因为并查集不能删边,用可撤销并查集(按秩合并),用栈维护并查集的操作就好了。

    按秩合并??:把高度小的树合并到高度大的树上,如果高度一样,合并后高度就 +1

    保证了树的高度最多为 log n

    按秩合并复杂度:(O(log~n)),线段树:(O(m~log~k))

    总的复杂度:(O(logn imes mlogn))

    code

    /*
    work by:Ariel_
    */
    #include <iostream>
    #include <cstring>
    #include <cstdio>
    #include <stack>
    #include <vector>
    #include <utility>
    #define ll long long
    #define rg register
    #define lson rt << 1
    #define rson rt << 1|1
    using namespace std;
    const int N = 1e5 + 5;
    const int M = 2e5 + 5;
    int read(){
        int x = 0,f = 1; char c = getchar();
        while(c < '0'||c > '9') {if(c == '-') f = -1; c = getchar();}
        while(c >= '0' && c <= '9') {x = x*10 + c - '0'; c = getchar();}
        return x*f;
    }
    struct node{int x, fag;};
    int n, m, fa[N << 1], a[N << 1], k, h[N << 1], u[M], v[M];
    struct Tree{int l, r; vector<int> e;}tree[N << 3];
    stack<node> s;
    int get_father(int x) { 
       while(x ^ fa[x]) x = fa[x];
       return x;
    }
    void merge(int x, int y) {
       if (x == y) return ;
       if (h[x] > h[y]) swap(x, y);
       s.push((node){x, h[x] == h[y]}), fa[x] = y, h[y] += h[x] == h[y]; //如果两棵树高度相同,那么合并之后高度会 + 1 
    }
    void build(int rt, int l, int r) {
       tree[rt].l = l, tree[rt].r = r;
       if (l == r) return;
       int mid = (l + r) >> 1;
       build(lson, l, mid), build(rson, mid + 1, r);
    }
    void ins(int rt, int L, int R, int id) {
        if (L <= tree[rt].l && tree[rt].r <= R) {
          tree[rt].e.push_back(id);
          return;
    	}
    	int mid = (tree[rt].l + tree[rt].r) >> 1;
    	if (L <= mid) ins(lson, L, R, id);
    	if (R > mid) ins(rson, L, R, id);
    }
    void DFS(int rt, int l, int r) {
        bool op = 1;//判断合不合法
    	int mid = (l + r) >> 1, siz = (int)s.size();
    	for (int i = 0; i < (int)tree[rt].e.size(); i++) {
    	    int x = tree[rt].e[i], u = get_father(::u[x]), v = get_father(::v[x]);
    	    if (u == v) { 
    	       for (int i = l; i <= r; i++) printf("No
    ");
    		   op = 0; break; 
    		}
    		merge(u, get_father(::v[x] + N)), merge(v, get_father(::u[x] + N));
    	} 
    	if (op) {
    	   if (l == r) printf("Yes
    ");
    	   else DFS(lson, l, mid), DFS(rson, mid + 1, r);
    	}
    	while (s.size() > siz) {
    	    node t = s.top();
    	    int x = t.x, fag = t.fag;
    		h[fa[x]] -= fag, fa[x] = x, s.pop();
    	}
    }
    int main(){
       n = read(), m = read(), k = read();
       for (int i = 1; i <= n; i++) fa[i] = i, fa[i + N] = i + N;
       build(1, 1, k);
       for (int i = 1, l, r; i <= m; i++) {
       	 u[i] = read(), v[i] = read(), l = read(), r = read();
       	 if(l ^ r) ins(1, l + 1, r, i);   
       }  
       DFS(1, 1, k);
       return 0;
    }
    
    
  • 相关阅读:
    ubuntu 安装QT 5.0出现错误:Failed to load platform plugin "xcb".
    Ubuntu手动编译GCC
    C++面试题集锦(一)
    C++排序系列(一) 插入排序之折半插入排序
    关于内存对齐的面试题
    C++中的static_cast, dynamic_cast和reinterpret_cast
    C++排序系列(二) 交换排序之简单排序
    Cannot open include file: 'iphlpapi.h': No such file or directory(最终解决方法)
    C++排序系列(一) 插入排序之直接插入排序
    ubuntu aptget update时出现W: GPG 错误
  • 原文地址:https://www.cnblogs.com/Arielzz/p/15054803.html
Copyright © 2011-2022 走看看