【8.13】点分治
点分治
点分治 是一种解决树上统计问题的常用方法,本质思想就是选择一点作为分治中心,将原问题划分为几个相同的子树上的问题,进行递归解决。
常见题目中给出的树都是无根树(所需维护的信息与根节点是谁无关)。
常见的用于统计树上有关路径的信息。假设当前选定根节点为 \(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;
}
。。。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· Manus爆火,是硬核还是营销?
· 终于写完轮子一部分:tcp代理 了,记录一下
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通