点分治 学习笔记
前言
int08 于 2024 年 5 月 30 日才通过点分治模板,望周知。
点分治 学习笔记!
过了一会之后,我的肩膀开始有些疼了,我的 osu! 瘾也犯了,所以我先暂时把这个当做第一集,各位如果想看第二集,可以等待我
打完 osu! 以及对点分治有更深的理解之后。
解决问题:点分治适合处理大规模的树上路径信息问题。
复杂度:模板
此处应有我自己对于“大规模的树上路径信息问题”的理解。
“树上”:点分治是树上问题的算法。
“路径”:就像分治处理点对一样,点分治处理树上点对,而树上点对自然涉及到它们之间的路径。
“信息”:信息通常与路径相关。(如果信息与异或相关,则一般不考虑点分治,因为路径异或可以通过求前缀和转为点权异或)
“大规模”:这是重点内容,我分两个方向理解,东北方一个,东方一个。
- 大规模表示不是处理具体给定的路径的问题,比如 P3384 【模板】重链剖分/树链剖分这样的问题并不适合点分治。不过这一条不绝对。
大规模更偏向于:
-
询问存不存在一种路径
-
找到所有路径中某种特征值最大/小的。
-
符合条件的路径计数
这种和
- 大规模不一定是要答案全局统计,也有可能是对于每个点分别统计相关点对答案。
因为点分治的每一轮,其实都是遍历了每个点的(复杂度只有一个
算法流程
把普通的分治套在树上,点分治的流程就显然应该是下面这样:
-
考虑每个子树的点与其它子树里的点之间的贡献(包括与根的贡献)。
-
递归进入子树考虑问题。
现在如果按照普通的分治逻辑思考就会出现三个问题:
-
普通分治只分成两块,所以可以确定左边一个,右边一个,这里会分成子树数量个,枚举两点属于的子树就会被菊花图卡成
。 -
普通分治中并没有单独考虑根这一环节。
-
碰到链这种结构,子树始终只有
的,递归次数达到 又要被卡回去。
我们依次解决。
- 第一个点枚举具体属于哪个子树是需要的,而枚举第二个点属于的子树并没有意义,我们只需要知道它不在原子树中即可。
如果枚举无序点对相关:钦定一个枚举顺序,把已经枚举过的部分扔到一起处理,如扔进桶,树状数组,Trie 树中。
如果枚举有序点对:如以某个点为起点一类,考虑将所有元素扔一个结构中,枚举某个子树答案时候从这个结构中删除,但是从两个方向分别枚举一般更加方便。
-
这就很简单了,在枚举开始之前就加上根的影响,如果寻找有序点对,根拉出来单独计算。
-
这也是点分治的核心问题。我们考虑每次选取树的重心为根,因为树的重心最大子树不超过
,只能递归 层,点分治的复杂度便得到了保证。
说起来简单,写起来还是有一些难度的
具体实现流程
这里以 P3806 【模板】点分治 1 为例简述实现过程。
给定一棵有
流程咕咕咕。
Talk is cheap,show me the code.
Upd on 2024.7.16:我回来做点分树的时候吃了不熟悉点分治的亏花了很久才过,为了防止将来的我忘掉点分治,现在我决定加上一些注释。
点击查看代码
#include<bits/stdc++.h>
using namespace std;
#define N 11451
int vis[N]; //很重要,点分治作为重心之后就标记,接下来就不再去。
bool tong[N*N];
int k;
int n,q;
vector<pair<int,int> > e[N];
int sz[N],mxs[N],rt; //这里的 size 和 mxs 对应第一遍 dfs 求重心,rt 就是重心,注意它全局变量的特殊性。
vector<int> col[N]; //将每个子树对应的深度集合放在一起,便于计算
void dfs1(int u,int fa,int n) //求重心,注意不要用二分之 n 求,有神秘错误。
{
sz[u]=1;mxs[u]=0;
for(auto v:e[u]) if(vis[v.first]==0&&v.first!=fa) dfs1(v.first,u,n),sz[u]+=sz[v.first],mxs[u]=max(sz[v.first],mxs[u]);
mxs[u]=max(mxs[u],n-sz[u]);
if(mxs[u]<=mxs[rt]) rt=u;
}
void dfs2(int u,int d,int co,int fa) //只用于分好很多深度集合。
{
col[co].push_back(d);
for(auto v:e[u]) if(v.first!=fa&&vis[v.first]==0) dfs2(v.first,d+v.second,co,u);
}
bool solve(int r,int n) //r 不是 点分治的重心,只是表示计算这个连通块
{
if(n==1) return 0;
rt=0;mxs[0]=998244353; //由于 mxs 和 rt 是全局变量,需要重置
dfs1(r,0,n);
int co=0;
for(auto v:e[rt]) if(!vis[v.first]) dfs2(v.first,v.second,++co,rt);
tong[0]=1;
bool ans=0;
for(int i=1;i<=co;i++) //具体计算的过程,一个一个遍历集合,加入桶,和之前的桶内元素比较。
{
for(auto x:col[i]) if(x<=k&&tong[k-x]) ans=1;
for(auto x:col[i]) tong[x]=1; //先比后加
}
for(int i=1;i<=co;i++)
{
for(auto x:col[i]) tong[x]=0;
col[i].clear(); //清空,消除影响。
}
if(ans) return ans; //只是剪枝,计数时候要去掉。
vis[rt]=1;
for(auto v:e[rt]) if(!vis[v.first])
{
if(sz[v.first]<sz[rt]) ans|=solve(v.first,sz[v.first]);
else ans|=solve(v.first,n-sz[rt]); //这里分讨以传入正确的大小
if(ans) return ans;
}
return 0;
}
int main()
{
cin>>n>>q;
int u,v,w;
for(int i=1;i<n;i++) cin>>u>>v>>w,e[u].push_back({v,w}),e[v].push_back({u,w});
while(q--)
{
cin>>k;
cout<<(solve(1,n)?"AYE":"NAY")<<endl;
memset(vis,0,sizeof(vis));
}
return 0;
}
本文作者:Fun_Strawberry's blog
本文链接:https://www.cnblogs.com/FunStrawberry/p/18223825
版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步