点分治——学习笔记

题目特征

点分治用于解决树上不带修改的路径问题(静态问题)

点分治

r o o t root 为当前的树根,则对于所有路径来说,有两种情况:
①经过 r o o t root 的路径
②不经过 r o o t root 的路径
对于①类路径来说,可以看做从 r o o t root 前往不同子树的两条路径之和(下面介绍的和都是指两点间距离)
在这里插入图片描述
对此处路径 ( 4 , 7 ) (4, 7) 来说,可以看做是路径 ( 4 , 1 ) (4,1) 和路径 ( 1 , 7 ) (1,7) 之和
若是即 d i s [ i ] dis[i] 为点 i i 到达当前树根的距离,那么 d i s t a n c e ( 4 , 7 ) = d i s [ 4 ] + d i s [ 7 ] = 4 distance(4,7)=dis[4]+dis[7]=4
还有要注意是不同子树,如果这里求的是 d i s t a n c e ( 6 , 7 ) distance(6,7) ,那么显然 d i s t a n c e ( 6 , 7 ) = 2 distance(6,7)=2 不等于 d i s [ 6 ] + d i s [ 7 ] = 4 dis[6]+dis[7]=4

现在看看怎么处理②类路径
对于②类路径来说,它一定可以在当前 r o o t root 的子树中找到一个点作为新的 r o o t root 来求得
d i s t a n c e ( 6 , 7 ) distance(6,7) 在以 3 3 为根时有, d i s t a n c e ( 6 , 7 ) = 2 = d i s [ 6 ] + d i s [ 7 ] distance(6,7)=2=dis[6]+dis[7]

所以②类路径可以看做是子树中的①类路径,这样所有路径都可以转化为①类路径
那么我们只需要以每一个点都作为 r o o t root 求①类路径就能求得所有的路径

那么怎么做呢,我们这里可以看到,每次处理的都是同一个问题——求①类路径
我们这里就是一个普通的 d f s dfs 递归过程而已,中间顺带处理了①类路径问题,每次处理的手法还是一模一样的

复杂度

这里我们处理树各层需要的复杂度为,第一层 O ( s i z e [ 1 ] ) = O ( N ) O(size[1])=O(N) ,第二层 O ( s i z e [ 2 ] ) + O ( s i z e [ 3 ] ) = O ( N 1 ) O(size[2])+O(size[3])=O(N-1) ,第三层 i = 4 7 O ( s i z e [ i ] ) = O ( N 3 ) \displaystyle\sum_{i=4}^7 O(size[i])=O(N-3) ,所以处理每一层可以看做是 O ( N ) O(N)
则递归层数为T,那么总体复杂度就是 O ( T N ) O(TN)

若我们处理的树形结构变成了线性结构,并且我们从端头开始递归,那么复杂度将会到达 O ( N 2 ) O(N^2)

所以这里我们要降低我们的递归层数
这里就需要我们求树的重心,这是因为树的重心有一个性质:
以重心为根,所有的子树的大小都不超过整个树大小的一半
这样每次递归的时候,先找出子树的重心,从重心进入,最后能保证我们递归层数在 l o g N logN
我对这里递归层数的理解:递归层数和树的大小有关,这里就假设每次最大的子树恰好为原树的一半(不超过一半),那么这个最大的子树中,最大的子树也是一半…以此类推到最后只需要 l o g N logN 次就能到达叶节点
( 2 l o g 2 N = N 2^{log_2N}=N )

故复杂度为 O ( N l o g N ) O(NlogN)

模板

int maxp[MAX], siz[MAX], vis[MAX], rt;

void getRt(int u, int fa, int all) {//求树的重心
    siz[u] = 1, maxp[u] = 0;
    for (int i = head[u], v = e[i].to; i; i = e[i].nxt, v = e[i].to)
        if (v != fa && !vis[v]) {
            getRt(v, u, all);
            siz[u] += siz[v];
            maxp[u] = max(maxp[u], siz[v]);
        }
    maxp[u] = max(maxp[u], all - siz[u]);//当前点为根的话,fa那边的子树就是all-siz[u]
    if (maxp[u] < maxp[rt]) rt = u;
}

void calc(int u) {//具体题目具体分析

}

void dfs(int u) {
    vis[u] = 1;
    calc(u);//计算第一类问题
    for (int i = head[u], v = e[i].to; i; i = e[i].nxt, v = e[i].to)
        if (!vis[v]) {
            maxp[rt = 0] = N; getRt(v, 0, siz[v]);//每次都要求重心
            dfs(rt);
        }
}

int main() {
	//前面预处理
    maxp[rt = 0] = N; getRt(1, 0, N);
    dfs(rt);//dfs求解答案
    return 0;
}

练习题

1. P3806 【模板】点分治1

题意

N N 个点的树, M M 次询问,每次问长度为 K K 的路径是否存在

分析

K 1 0 7 K\leq10^7 ,我们这里可以用桶来记录一下之前找到的路径长度
每次从子树中找到路径,但先不加入桶中,因为我们要保证两个路径不在同一个子树里面
若某路径长度为 d i s dis ,那么只需要看桶 f [ K d i s ] f[K-dis] 是否为1即可
还有最开始要往桶中加入 0 0 这个长度,这样不会漏掉直接从 r o o t root 到那个点的路径

代码

void getDis(int u, int fa, int d) {
    if (d < MAX_N) t[++t[0]] = d;//每次将当前路径记录在t数组中
    for (int i = head[u], v = e[i].to; i; i = e[i].nxt, v = e[i].to)
        if (v != fa && !vis[v]) getDis(v, u, d + e[i].w);
}

void calc(int u) {
    int num = 0;
    f[0] = 1;//长度为0的可以
    for (int i = head[u], v = e[i].to; i; i = e[i].nxt, v = e[i].to)
        if (!vis[v]) {
            t[0] = 0; getDis(v, u, e[i].w);//找当前子树里面的路径
            for (int j = 1; j <= M; j++)//对于每一个问题,找答案
                if (!ans[j]) {//如果没找到
                    for (int k = 1; k <= t[0]; k++)//从这个子树里面的路径开始
                        if (query[j] - t[k] >= 0)
                            ans[j] |= f[query[j] - t[k]];
                }
            for (int j = 1; j <= t[0]; j++)//先找完再加到桶里面,这样能保证之前找的路径和本次的不在同一个子树中
                f[t[j]] = 1, store[++num] = t[j];//因为最后memset桶会超时,所以这里用一个store来记录改了哪里
        }
    for (int i = 1; i <= num; i++)
        f[store[i]] = 0;//最后清空
}

2. P4178 Tree

题意

给你一棵TREE,以及这棵树上边的距离.问有多少对点它们两者间的距离小于等于 K K

分析

当前路径为 d i s dis ,这里我用树状数组来查路径长度小于等于 K d i s K-dis 的路径数量
一样的处理,先找路径,然后先查询在将路径加入树状数组
还有因为树状数组不能查长度为 0 0 路径,所以我把所有路径长度都+1,那么 K K 应该是+2,而不是+1

代码

void getDis(int u, int fa, int d) {//这里我K已经提前+2了
    if (d <= K - 2) t[++t[0]] = d + 1;//路径长度+1
    for (int i = head[u], v = e[i].to; i; i = e[i].nxt, v = e[i].to)
        if (v != fa && !vis[v]) getDis(v, u, d + e[i].w);
}

void calc(int u) {
    int num = 0;
    upd(1, 1);//加入路径长度为0, 此处路径长度+1, 所以是1
    for (int i = head[u], v = e[i].to; i; i = e[i].nxt, v = e[i].to)
        if (!vis[v]) {
            t[0] = 0; getDis(v, u, e[i].w);//先找路径
            for (int j = 1; j <= t[0]; j++)//查询
                if (K - t[j] >= 0) ans += query(K - t[j]);
            for (int j = 1; j <= t[0]; j++)//再加入树状数组
                upd(t[j], 1), store[++num] = t[j];//更新,并记录加了哪些值
        }
    upd(1, -1);//最后清空
    for (int i = 1; i <= num; i++)
        upd(store[i], -1);
}

3. P4149 [IOI2011]Race

题意

给一棵树,每条边有权。求一条简单路径,权值和等于 K K ,且边的数量最小

分析

和前面差不多,多记一个边数就行
f [ i ] f[i] 同样是桶,记得是长度为 i i 的最小边数的路径

代码

void getDis(int u, int fa, int d, int num) {
    if (d <= K) t[++p] = make_pair(d, num);
    for (int i = head[u], v = e[i].to; i; i = e[i].nxt, v = e[i].to)
        if (v != fa && !vis[v]) getDis(v, u, d + e[i].w, num + 1);
}

void calc(int u) {
    int num = 0;
    for (int i = head[u], v = e[i].to; i; i = e[i].nxt, v = e[i].to)
        if (!vis[v]) {
            p = 0; getDis(v, u, e[i].w, 1);
            for (int j = 1; j <= p; j++)
                if (K >= t[j].first && f[K - t[j].first] != INF)
                    ans = min(ans, f[K - t[j].first] + t[j].second);
            for (int j = 1; j <= p; j++)
                f[t[j].first] = min(f[t[j].first], t[j].second), store[++num] = t[j].first;
        }
    for (int i = 1; i <= num; i++)
        f[store[i]] = INF;
}

4. P3714 [BJOI2017]树的难题

分析

这里比较复杂,要考虑两条路径合并的时候合并的部分颜色是否相同
并且要查询 [ L d i s , R d i s ] [L-dis,R-dis] 中边权最大的
所以这里建了两个线段树维护最大值,一个是颜色相同的 s a m ( s a m e ) sam(same) 线段树,一个颜色不同的 d i f ( d i f f e r e n t ) dif(different) 线段树
我们需要先将边排序,按照颜色排序,这样一旦当前边的颜色和上一个边的颜色不同的时候,说明上一种颜色已经处理完毕,就可以清空线段树,然后将之前的这种颜色全部放入 d i f dif 线段树(因为这个时候前面那种颜色和当前这种颜色是不一样的)

代码

代码比较复杂,放全部的

#include <bits/stdc++.h>
using namespace std;
#define INF 0x3f3f3f3f
#define lc  u<<1
#define rc  u<<1|1
#define m   (l+r)/2
typedef long long ll;
typedef pair<int, int> pii;
const int MAX = 2e5 + 10;

struct edge {
    int to, color;
    bool operator < (const edge &rhs) const {
        return color < rhs.color;
    }
};

int N, M, L, R;
int val[MAX], ans, cnt;
vector<edge> g[MAX];
int maxp[MAX], siz[MAX], vis[MAX], rt;
pii t[MAX];
vector<pii> tmp;

struct Tree {
    struct SegementTree {
        int mx, tag;//mx记录当前最大, tag记录是否要清空
        void upd() {
            mx = -INF;
            tag = 1;
        }
    } t[MAX << 1];
    void push_up(int u) { t[u].mx = max(t[lc].mx, t[rc].mx); }
    void push_down(int u) {
        if (t[u].tag) {
            t[lc].upd();
            t[rc].upd();
            t[u].tag = 0;
        }
    }
    void update(int u, int l, int r, int p, int k) {
        if (l == r) {
            t[u].mx = max(t[u].mx, k);
            return;
        }
        push_down(u);
        if (p <= m) update(lc, l, m, p, k);
        else update(rc, m + 1, r, p, k);
        push_up(u);
    }
    int query(int u, int l, int r, int ql, int qr) {
        if (ql <= l && r <= qr) return t[u].mx;
        if (t[u].tag) return -INF;
        int res = -INF;
        if (ql <= m) res = max(res, query(lc, l, m, ql, qr));
        if (qr > m) res = max(res, query(rc, m + 1, r, ql, qr));
        return res;
    }
    void clear() { t[1].upd(); }
} sam, dif;

void getRt(int u, int fa, int all) {
    siz[u] = 1, maxp[u] = 0;
    for (auto &i: g[u]) {
        int v = i.to;
        if (v != fa && !vis[v]) {
            getRt(v, u, all);
            siz[u] += siz[v];
            maxp[u] = max(maxp[u], siz[v]);
        }
    }
    maxp[u] = max(maxp[u], all - siz[u]);
    if (maxp[u] < maxp[rt]) rt = u;
}

void getRoad(int u, int fa, int nowc, int nume, int dis) {
    if (nume >= L) ans = max(ans, dis);//路径长度>=L直接判,因为线段树不处理长度0的路径,所以直接判
    if (nume == R) return;//长度为R的找的对应路径长度为0, 这里我不存, 后面就没啥意义了, 所以直接返回
    t[++cnt] = make_pair(nume, dis);
    for (auto &i: g[u]) {
        int v = i.to;
        if (v != fa && !vis[v]) getRoad(v, u, i.color, nume + 1, dis + (nowc == i.color ? 0 : val[i.color]));
    }
}

void calc(int u) {
    int pre = 0;
    sam.clear();
    dif.clear();
    tmp.clear();
    for (auto &i: g[u]) {
        int v = i.to;
        if (!vis[v]) {
            cnt = 0; getRoad(v, u, i.color, 1, val[i.color]);
            if (pre == i.color) {//颜色一样
                //先查
                for (int j = 1; j <= cnt; j++) ans = max(ans, dif.query(1, 1, R - 1, max(1, L - t[j].first), R - t[j].first) + t[j].second);
                for (int j = 1; j <= cnt; j++) ans = max(ans, sam.query(1, 1, R - 1, max(1, L - t[j].first), R - t[j].first) + t[j].second - val[i.color]);
                //将点加入颜色相同的sam线段树,并且记录加了哪些点,这样后面直接再加入dif线段树
                for (int j = 1; j <= cnt; j++) sam.update(1, 1, R - 1, t[j].first, t[j].second), tmp.push_back(t[j]);
            }
            else {
                //将之前颜色相同的点都加入dif线段树
                for (auto &j: tmp) dif.update(1, 1, R - 1, j.first, j.second);
                //要先加tmp再查, 因为前面那些颜色已经和当前颜色不一样了
                for (int j = 1; j <= cnt; j++) ans = max(ans, dif.query(1, 1, R - 1, max(1, L - t[j].first), R - t[j].first) + t[j].second);
                tmp.clear();//清空tmp
                sam.clear();//清空sam线段树
                //当前这个颜色是新的颜色,加入sam线段树
                for (int j = 1; j <= cnt; j++) sam.update(1, 1, R - 1, t[j].first, t[j].second), tmp.push_back(t[j]);
            }
            pre = i.color;//上一种颜色
        }
    }
}

void dfs(int u) {
    vis[u] = 1;
    calc(u);
    for (auto &i: g[u]) {
        int v = i.to;
        if (!vis[v]) {
            maxp[rt = 0] = N; getRt(v, 0, siz[v]);
            dfs(rt);
        }
    }
}

int main() {

    scanf("%d%d%d%d", &N, &M, &L, &R);
    for (int i = 1; i <= M; i++) scanf("%d", &val[i]);
    for (int i = 1; i < N; i++) {
        int u, v, color; scanf("%d%d%d", &u, &v, &color);
        g[u].push_back(edge{v, color});
        g[v].push_back(edge{u, color});
    }
    for (int i = 1; i <= N; i++) sort(g[i].begin(), g[i].end());//将边按颜色排序
    maxp[rt = 0] = N; getRt(1, 0, N);
    ans = -INF;
    dfs(rt);
    printf("%d\n", ans);


    return 0;
}


猜你喜欢

转载自blog.csdn.net/weixin_44282912/article/details/104476059
今日推荐