「点分治」学习笔记

点分治能够快速处理树上路径问题。

在考虑路径问题时,可以利用分治的思想。选出一个根节点,先考虑所有经过根节点的路径。路径的起点和终点分别在根节点的两个子树上。解决之后,要解决的就是各个子树的路径问题了。我们再找出每个子树的根节点,然后一样递归处理即可。

问题在于应该选哪个节点作为子树的根节点。最优方法是选取树的重心。因为重心有一个性质,它的各个子树的大小都不超过总大小的一半。这样每一次都分为若干个不超过一半的子问题,就变成了分治算法。分治次数为$O( \log n)$。如果想要达成$O(n \log n)$的复杂度,那对于每个节点的计算就要$O(n)$完成

Code

有一些细节需要注意。

/*DennyQi 2019*/
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <queue>
using namespace std;
const int N = 10010;
inline int read(){
    int x(0),w(1); char c = getchar();
    while(c^'-' && (c<'0' || c>'9')) c = getchar();
    if(c=='-') w = -1, c = getchar();
    while(c>='0' && c<='9') x = (x<<3)+(x<<1)+c-'0', c = getchar(); 
    return x*w;
}
int n,m,x,y,z,S,rt,cnt,test[N],vis[N],sz[N],maxp[N],judge[10000010],rem[N],q[N],Q[N];
int head[N],nxt[N<<1],to[N<<1],cost[N<<1],cnte;
inline void add(int u, int v, int w){
    to[++cnte] = v;
    cost[cnte] = w;
    nxt[cnte] = head[u];
    head[u] = cnte;
}
void getrt(int u, int Fa){
    sz[u] = 1;
    maxp[u] = 0;
    for(int i = head[u]; i; i = nxt[i]){
        if(to[i] == Fa) continue;
        if(vis[to[i]]) continue;
        getrt(to[i],u);
        sz[u] += sz[to[i]];
        maxp[u] = max(maxp[u],sz[to[i]]);
    }
    maxp[u] = max(maxp[u],S-sz[u]);
    if(maxp[u] < maxp[rt]) rt = u;
}
void getdis(int u, int Fa, int d){
    rem[++cnt] = d;
    for(int i = head[u]; i; i = nxt[i]){
        if(to[i] == Fa) continue;
        if(vis[to[i]]) continue;
        getdis(to[i],u,d+cost[i]);
    }
}
inline void calc(int u){
    int p = 0;
    for(int i = head[u]; i; i = nxt[i]){
        if(vis[to[i]]) continue;
        cnt = 0;
        getdis(to[i],u,cost[i]);
        for(int j = 1; j <= cnt; ++j){
            for(int k = 1; k <= m; ++k){
                if(Q[k] >= rem[j]){
                    test[k] |= judge[Q[k]-rem[j]];
                }
            }
        }
        for(int j = 1; j <= cnt; ++j){
            q[++p] = rem[j];
            judge[rem[j]] = 1;
        }
    }
    for(int i = 1; i <= p; ++i) judge[q[i]] = 0;
}
void solve(int u){
    vis[u] = 1;
    judge[0] = 1;
    calc(u);
    for(int i = head[u]; i; i = nxt[i]){
        if(vis[to[i]]) continue;
        S = sz[to[i]];
        maxp[rt = 0] = 0x3f3f3f3f;
        getrt(to[i],-1);
        solve(rt);
    }
}
int main(){
    // freopen("file.in","r",stdin);
    n = read(), m = read();
    for(int i = 1; i < n; ++i){
        x = read(), y = read(), z = read();
        add(x,y,z);
        add(y,x,z);
    }
    for(int i = 1; i <= m; ++i) Q[i] = read();
    S = n;
    maxp[rt] = 0x3f3f3f3f;
    getrt(1,-1);
    solve(rt);
    for(int i = 1; i <= m; ++i){
        if(test[i]) printf("AYE\n"); else printf("NAY\n");
    }
    return 0;
}

 

 

 

带权树上长度为K的路径是否存在(洛谷P3806):点分治时记录每个根节点出发能达到的距离,开桶记录即可。

带权树上长度为3的倍数的路径条数(洛谷P2634):只需要记录一下模三的剩余系中的路径条数就好了。

带权树上长度为K的路径的最小边数(洛谷P4149):只需要维护的距离的时候多维护一个边数就好了。

带权树上长度$\leq$K的路径条数(洛谷P4178):暴力统计前缀和会被菊花图卡爆。用树状数组维护,多一只$\mathrm{log}$。

posted @ 2019-07-02 08:41  DennyQi  阅读(258)  评论(0编辑  收藏  举报