树分治

点分治

1、通过重儿子这个特性,每次遍历重儿子,每次找到的重儿子都能将重儿子的每个子节点的子树都几乎平均分割为 \(log\) 大小,所以最多 \(log\) 层,每层差不多 \(n\) 个节点,最终时间复杂度就为 \(nlogn\)

2、可以用于解决两点之间的距离为 \(k\) 的数目有多少等等。

3、时间复杂度:\(O(nlogn)\),空间复杂度:\(O(n)\)

4、模版:洛谷P3806

template<typename T>
struct tree_div{
	struct node{
		int to, nxt;
		T w;
        node(int to = 0, int nxt = 0, T w = 0) : to(to), nxt(nxt), w(w) {}
	};
	
	int n, m, cnt, q, sum, root, k, tot;
    queue<T> tag;
	vector<node> edg;
	vector<T> head, vis, res, Q, mar;
	vector<T> siz, siz_mx, dist, sto;
	
	tree_div(int n_, int m_, int q_, int k_ = 10000000) 
    : head(n_ + 1), siz(n_ + 1, 1), siz_mx(n_ + 1), 
    dist(n_ + 1), vis(k_ + 1), res(q_ + 1), Q(q_ + 1),
    mar(n_ + 1), sto(n_ + 1), edg(m_ << 1 | 1) {
		cnt = 0;
        tot = 0;
        sum = n_;
		n = n_;
		m = m_;
        q = q_;
        k = k_;
	}
	
	inline void add(int u, int v, const T &w) {
		++cnt;
		edg[cnt].to = v;
		edg[cnt].nxt = head[u];
		edg[cnt].w = w;
		head[u] = cnt;
	}
	
	inline void init(int n_, int m_, int q_, int k_ = 10000000) {
		cnt = 0;
        tot = 0;
        sum = n_;
		n = n_;
		m = m_;
        q = q_;
        k = k_;
        head.assign(n_ + 1, 0);
        siz.assign(n_ + 1, 1);
        siz_mx.assign(n_ + 1, 0);
        dist.assign(n_ + 1, 0);
        vis.assign(k_ + 1, 0);
        res.assign(q_ + 1, 0);
        Q.assign(q_ + 1, 0);
        mar.assign(n_ + 1, 0);
        sto.assign(n_ + 1, 0);
        edg.assign(m_ << 1 | 1, node());
    }
	
	inline void calcsiz(int u, int pa) {// O(n)的时间复杂度得到重心
        siz[u] = 1;
        siz_mx[u] = 0;
		for (int i = head[u]; i; i = edg[i].nxt) {
			int v = edg[i].to;
			T w = edg[i].w;
			if (v == pa || mar[v]) {
                continue;
            }
			calcsiz(v, u);
			siz[u] += siz[v];
			siz_mx[u] = max(siz_mx[u], siz[v]);
		}
		
		siz_mx[u] = max(siz_mx[u], sum - siz[u]);
        if (siz_mx[u] < siz_mx[root]) {
            root = u;
        }
	}

    inline void calcdist(int u, int pa) {// 计算重心的每个子节点中到重心的距离
        sto[++tot] = dist[u];
        for (int i = head[u]; i; i = edg[i].nxt) {
            int v = edg[i].to;
            T w = edg[i].w;
            if (v == pa || mar[v]) {
                continue;
            }
            dist[v] = dist[u] + w;
            calcdist(v, u);
        }
    }

    inline void dfz(int u, int pa) {
        vis[0] = 1;// 根距离为0
        tag.push(0);
        mar[u] = 1;// 标记重心,表示经过这个点的路径都跑完了
        for (int i = head[u]; i; i = edg[i].nxt) {
            int v = edg[i].to;
            T w = edg[i].w;
            if (v == pa || mar[v]) {
                continue;
            }
            dist[v] = w;
            calcdist(v, u);
            for (int j = 1; j <= tot; j++) {
                for (int z = 1; z <= q; z++) {// 处理查询的所有点
                    if (Q[z] >= sto[j]) {
                        res[z] |= vis[Q[z] - sto[j]];
                    }
                }
            }
            for (int j = 1; j <= tot; j++) {
                if (sto[j] <= k) {
                    tag.push(sto[j]);
                    vis[sto[j]] = 1;// 标记距离值
                }
            }
            tot = 0;
        }

        while (!tag.empty()) {// 去除之前找的所有值
            vis[tag.front()] = 0;
            tag.pop();
        }

        for (int i = head[u]; i; i = edg[i].nxt) {
            int v = edg[i].to;
            T w = edg[i].w;
            if (v == pa || mar[v]) {
                continue;
            }
            sum = siz[v];
            root = 0;
            siz_mx[root] = mof;
            calcsiz(v, u);// 重新找新的重心节点
            calcsiz(root, 0);
            dfz(root, u);
        }
    }

    inline void deal() {
        root = 0;
        siz_mx[root] = mof;
        calcsiz(1, 0);
        calcsiz(root, 0);
        dfz(root, 0);
    }
};

void solve() {
    int n, m;
    std::cin >> n >> m;
    tree_div<int> tr(n, n - 1, m);
    for (int i = 1, u, v, w; i < n; i++) {
        std::cin >> u >> v >> w;
        tr.add(u, v, w);
        tr.add(v, u, w);
    }
    for (int i = 1; i <= m; i++) {
        std::cin >> tr.Q[i];
    }
    tr.deal();
    for (int i = 1; i <= m; i++) {
        std::cout << (tr.res[i] ? "AYE" : "NAY") << '\n';
    }
}

拓展

1、题目:给定一棵 \(n\) 个节点的树,每条边有边权,求出树上两点距离小于等于 \(k\) 的点对数量。

2、一样的,只不过这次需要用树状数组维护下权值数目。

3、例题:洛谷P4178

template<typename T>
struct Fenwick{
    int n;
    vector<T> a;
    
    Fenwick() {}
    Fenwick(int n_) : a(n_ + 1) {
        n = n_;
    }
    
    inline int low_bit(int x) {
        return x & -x;
    }
    
    inline void add(int x, const T &v) {
        for (int i = x; i <= n; i += low_bit(i)) {
            a[i] += v;
        }
    }
    
    inline T query(int x) {
        T sum{};
        for (int i = x; i >= 1; i -= low_bit(i)) {
            sum += a[i];
        }
        return sum;
    }
    
    inline T rangeQuery(int L, int R) {
        return query(R) - query(L - 1);
    }
    
    inline int select(const T &k) {
        int x = 0;
        T cnt{};
        for (int i = 1 << __lg(n); i; i >>= 1) {
            if (x + i <= n && cnt + a[i + x] <= k) {
                x += i;
                cnt += a[x];
            }
        }
        return x;
    }
};

template<typename T>
struct tree_div{
	struct node{
		int to, nxt;
		T w;
        node(int to = 0, int nxt = 0, T w = 0) : to(to), nxt(nxt), w(w) {}
	};
	
	int n, m, cnt, sum, root, k, tot, res;
    queue<T> tag;
	vector<int> head, mar;
	vector<T> siz, siz_mx, dist, sto;
	vector<node> edg;
    Fenwick<T> calc;
	
    tree_div() {}
	tree_div(int n_, int m_) 
    : head(n_ + 1), siz(n_ + 1, 1), siz_mx(n_ + 1), dist(n_ + 1), 
    mar(n_ + 1), sto(n_ + 1), edg(m_ << 1 | 1) {
		cnt = 0;
        tot = 0;
        res = 0;
        sum = n_;
		n = n_;
		m = m_;
	}
	
	inline void add(int u, int v, const T &w) {
		++cnt;
		edg[cnt].to = v;
		edg[cnt].nxt = head[u];
		edg[cnt].w = w;
		head[u] = cnt;
	}
	
	inline void init(int n_, int m_) {
        cnt = 0;
        tot = 0;
        res = 0;
        sum = n_;
		n = n_;
		m = m_;
        head.assign(n_ + 1, 0);
        siz.assign(n_ + 1, 1);
        siz_mx.assign(n_ + 1, 0);
        dist.assign(n_ + 1, 0);
        mar.assign(n_ + 1, 0);
        sto.assign(n_ + 1);
        edg.assign(m_ << 1 | 1, node());
        
	}
	
	inline void calcsiz(int u, int pa) {
        siz[u] = 1;
        siz_mx[u] = 0;
		for (int i = head[u]; i; i = edg[i].nxt) {
			int v = edg[i].to;
			T w = edg[i].w;
			if (v == pa || mar[v]) {
                continue;
            }
			calcsiz(v, u);
			siz[u] += siz[v];
			siz_mx[u] = max(siz_mx[u], siz[v]);
		}
		
		siz_mx[u] = max(siz_mx[u], sum - siz[u]);
        if (siz_mx[u] < siz_mx[root]) {
            root = u;
        }
	}

    inline void calcdist(int u, int pa) {
        sto[++tot] = dist[u];
        for (int i = head[u]; i; i = edg[i].nxt) {
            int v = edg[i].to;
            T w = edg[i].w;
            if (v == pa || mar[v]) {
                continue;
            }
            dist[v] = dist[u] + w;
            calcdist(v, u);
        }
    }

    inline void dfz(int u, int pa) {
        calc.add(1, 1);
        tag.push(0);
        mar[u] = 1;
        for (int i = head[u]; i; i = edg[i].nxt) {
            int v = edg[i].to;
            T w = edg[i].w;
            if (v == pa || mar[v]) {
                continue;
            }
            dist[v] = w;
            calcdist(v, u);
            for (int j = 1; j <= tot; j++) {
                if (sto[j] <= k) {
                    T val = k - sto[j];
                    res += calc.rangeQuery(1, val + 1);
                }
            }
            for (int j = 1; j <= tot; j++) {
                if (sto[j] <= k) {
                    tag.push(sto[j]);
                    calc.add(sto[j] + 1, 1);
                }
            }
            tot = 0;
        }

        while (!tag.empty()) {
            calc.add(tag.front() + 1, -1);
            tag.pop();
        }

        for (int i = head[u]; i; i = edg[i].nxt) {
            int v = edg[i].to;
            T w = edg[i].w;
            if (v == pa || mar[v]) {
                continue;
            }
            sum = siz[v];
            root = 0;
            siz_mx[root] = mof;
            calcsiz(v, u);
            calcsiz(root, 0);
            dfz(root, u);
        }
    }

    inline void deal() {
        std::cin >> k;
        calc = Fenwick<T>(k + 1);
        root = 0;
        siz_mx[root] = mof;
        calcsiz(1, 0);
        calcsiz(root, 0);
        dfz(root, 0);
    }
};

void solve() {
    int n;
    std::cin >> n;
    tree_div<int> tr(n, n - 1);
    for (int i = 1, u, v, w; i < n; i++) {
        std::cin >> u >> v >> w;
        tr.add(u, v, w);
        tr.add(v, u, w);
    }
    tr.deal();
    std::cout << tr.res << '\n';
}

边分治

posted @ 2024-08-29 09:19  grape_king  阅读(29)  评论(0)    收藏  举报