【8.13】点分治

点分治

参考blog

点分治 是一种解决树上统计问题的常用方法,本质思想就是选择一点作为分治中心,将原问题划分为几个相同的子树上的问题,进行递归解决。

常见题目中给出的树都是无根树(所需维护的信息与根节点是谁无关)。

常见的用于统计树上有关路径的信息。假设当前选定根节点为 \(rt\) ,则符合条件的路径必然是以下两种之一:

  • 经过 \(rt\) 或一端在 \(rt\) 上;
  • 不经过 \(rt\) ,在 \(rt\) 的子树上。

点分治仅仅是一种思想、方法,并没有固定的信息维护、转移方式。

树的重心

定义

在无根树中,以树的重心为根时,所有子树大小的最大值最小。

  • 在点分治时使用树的重心为根可以有效降低复杂度。

  • 把树的重心和每颗子树的重心连接即可构成点分树(深度为 \(\log n\))。

例题

【模板】点分治 1

给定一棵有 \(n\) 个点的树,询问树上距离为 \(k\) 的点对是否存在。

Solution

设当前分治的子树的根为 \(root\)

\(1.\) 寻找当前子树的重心并以它为根,寻找经过 \(root\) 且长度为 \(k\) 的路径是否存在。

\(2.\) 进入子树,重复上述操作。

Code

#include <bits/stdc++.h>
#define MAXN 10003
#define MAXM 103
#define MAXK 10000003
using namespace std;
int n,m;
int k[MAXM];
struct edge{
    int v;
    int w;
};
vector <edge> e[MAXN];
inline void add(int u,int v,int w){
    e[u].push_back({v,w});
    e[v].push_back({u,w});
    return;
}
int vis[MAXN];
int dis[MAXN],size[MAXN]; int sum; int root,rtsi;
inline void dfs(int u,int fa){
    size[u]=1;
    for (auto ed:e[u]){
        if (vis[ed.v]||ed.v==fa) continue;
        dis[ed.v]=dis[u]+ed.w;
        dfs(ed.v,u);
        size[u]+=size[ed.v];
    }
    int mxsi=0;
    for (auto ed:e[u]){
        if (vis[ed.v]||ed.v==fa) continue;
        mxsi=max(mxsi,size[ed.v]);
    }
    mxsi=max(mxsi,sum-size[u]);
    if (mxsi < rtsi){
        rtsi=mxsi; root=u;
    }
    return;
}
int cnt[MAXK];
int q[MAXN],r=0;
int q2[MAXN],r2=0;
int ans[MAXM];
inline void dfs3(int u,int rt){
    vis[u]=1;
    int len=dis[u]-dis[rt];
    for (int i=1;i<=m;i++){
        if (k[i]-len>=0) ans[i]+=cnt[k[i]-len];
    }
    q2[++r2]=len;
    for (auto ed:e[u]){
        if (vis[ed.v]) continue;
        dfs3(ed.v,rt);
    }
    vis[u]=0;
    return;
}
inline void dfs2(int u,int rt){
    vis[u]=1;
    cnt[0]=1;
    for (auto ed:e[u]){
        if (vis[ed.v]||ed.v==rt) continue;
        dfs3(ed.v,u);
        for (int i=1;i<=r2;i++) if (q2[i]<=10000000) cnt[q2[i]]++;
        while (r2) q[++r]=q2[r2--];
    }
    while (r) if (q[r]<=10000000) cnt[q[r--]]=0; else r--;
    for (auto ed:e[u]){
        if (vis[ed.v]||ed.v==rt) continue;
        sum=size[ed.v]; rtsi=INT_MAX;
        dfs(ed.v,rt); dfs(root,rt);
        dfs2(root,rt);
    }
    return;
}
int main(){
    // freopen("2.in","r",stdin);
    ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
    cin>>n>>m;
    for (int i=1;i<=n-1;i++){
        int u,v,w;
        cin>>u>>v>>w;
        add(u,v,w);
    }
    for (int i=1;i<=m;i++) cin>>k[i];
    sum=n; rtsi=INT_MAX;
    dfs(1,0);
    dfs(root,0);
    dfs2(root,0);
    for (int i=1;i<=m;i++){
        if (ans[i]) cout<<"AYE\n";
        else cout<<"NAY\n";
    }
    return 0;
}

。。。

posted @   EcapsXD  阅读(24)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· Manus爆火,是硬核还是营销?
· 终于写完轮子一部分:tcp代理 了,记录一下
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通
点击右上角即可分享
微信分享提示