【算法学习】点分治
1.【算法学习】排序2.【算法学习】Manacher 马拉车3.【算法学习】KMP 算法4.LCA 最近公共祖先(树链和倍增)这次真有树链了!!!5.线段覆盖问题6.【算法学习】学换根dp有感7.二分图最大匹配8.【算法学习】01BFS9.洛谷 P1892 [BOI2003] 团伙 种类并查集 扩展域并查集10.【算法学习】高斯消元法11.贝叶斯公式12.背包13.【算法学习】模拟退火14.【算法学习】基环树15.【算法学习】树链部分16.【算法学习】莫队17.【算法学习】分块九讲18.平衡树19.圆方树
20.【算法学习】点分治
21.公式22.【算法学习】笛卡尔树23.【算法学习】悬线法24.欧几里得算法与 EX25.【算法学习】逆元与求解26.【算法学习】费马定理27.三分28.裴蜀定理29.【算法学习】欧拉函数φ30.【算法学习】二维转一维问题31.【算法学习】扫描线32.【算法学习】矩阵乘法33.【算法学习】同余最短路34.【算法学习】组合数学35.【算法学习】反悔贪心感觉非常有深度,感觉过几天就又要忘了,所以我写个题解。
P3806 【模板】点分治 1
给定一棵有 \(n\) 个点的树,询问树上距离为 \(k\) 的点对是否存在。
题意非常简单 题意越短越毒瘤。
我们先想想点对有几种情况:
第一种是经过根节点的路径;
第二种是不经过根节点的路径;
想第一种有路径距离 \(dis(u,v)=dis(u,root)+dis(v,root)\)。
而第二种呢,只是到了他们的另一个公共祖先,即另一个根,发现是不是有点分治的感觉了,大子树分为小子树,依次处理点对距离。
但是不做任何优化的话可能会有一个很深的子树,看大佬的图
这时候依次处理就很慢了,此时我们就要找到重心(删掉重心后剩下的子树尽可能地平衡(分出来的最大子树的size尽可能地小))
然后就没了,反复找重心,处理距离就可以了,然后我就讲下代码如何实现吧。
找重心,找树中的每个点,找到一个节点最大子树所拥有的节点数尽可能小。
void getroot(int x,int f){ siz[x]=1;//树大小 dp[x]=0;//最大的子树 for(int i=head[x];i;i=e[i].next){ int y=e[i].v; if(vis[y]||y==f){//不能是父节点 continue; } getroot(y,x);//遍历子节点 siz[x]+=siz[y];//更新大小 dp[x]=max(dp[x],siz[y]);//最大子树的大小 } dp[x]=max(dp[x],sum-siz[x]);//父节点的子树大小也算 if(dp[x]<dp[root]){//最大的子树更小,更新重心为根 root=x; } }
我们找到了个根,想知道到所以点的距离怎么办,计算!!!
void getdis(int x,int f){ rev[++tot]=dis[x];//有这么一个距离(记住了) if(dis[x]>1e7){//洛谷机制路径太大不合法 return } for(int i=head[x];i;i=e[i].next){ int y=e[i].v; if(vis[y]||y==f){ continue; } dis[y]=dis[x]+e[i].w;//更新距离 getdis(y,x);//更新距离 } }
此时知道根到每个点的距离,那该处理答案了吧。
void doit(int x){ int c=0;//记录有多少个距离 for(int i=head[x];i;i=e[i].next){ int y=e[i].v; if(vis[y]){ continue; } tot=0;//有几个路径 dis[y]=e[i].w;//更新距离 getdis(y,x); for(int j=1;j<=tot;j++){ for(int k=1;k<=m;k++){//遍历询问 if(query[k]>=rev[j]){ pax[k]|=pd[query[k]-rev[j]];//如果有这么个值更新 } } } for(int j=1;j<=tot;j++){ pd[rev[j]]=1;//表示有这么个值 q[++c]=rev[j];//为了清除 } } for(int i=1;i<=c;i++){ pd[q[i]]=0;//清除,优化时间 } }
此时该向下递归根节点了。
void solve(int x){ vis[x]=1,pd[0]=1; doit(x);//处理当x为根,所有点对的距离 for(int i=head[x];i;i=e[i].next){ int y=e[i].v; if(vis[y]){ continue; } sum=siz[y];//设子树大小 root=0;//更新子树重心 getroot(y,x);//找新子树的重心 solve(root); } }
好了上面就是主要的代码了,你应该蒙了吧,确实会晕的,但菜就多练。
点击查看完整代码
#include <bits/stdc++.h> using namespace std; #define ll long long const int N=1e7+10; #define int ll int n,m; struct ss{ int v,w,next; }e[N]; int cnt=2,head[N]; void add(int u,int v,int w){ e[cnt].v=v; e[cnt].w=w; e[cnt].next=head[u]; head[u]=cnt++; } int query[N]; int vis[N],siz[N],root,dp[N],sum,pd[N]; int rev[N],q[N],dis[N],pax[N]; void getroot(int x,int f){ siz[x]=1; dp[x]=0; for(int i=head[x];i;i=e[i].next){ int y=e[i].v; if(vis[y]||y==f){ continue; } getroot(y,x); siz[x]+=siz[y]; dp[x]=max(dp[x],siz[y]);//最大子树的大小 } dp[x]=max(dp[x],sum-siz[x]);//父节点的子树大小也算 if(dp[x]<dp[root]){//找子树最小 root=x; } } int tot=0; void getdis(int x,int f){ rev[++tot]=dis[x];//记录距离 if(dis[x]>1e7){ return } for(int i=head[x];i;i=e[i].next){ int y=e[i].v; if(vis[y]||y==f){ continue; } dis[y]=dis[x]+e[i].w;//更新距离 getdis(y,x); } } void doit(int x){ int c=0; for(int i=head[x];i;i=e[i].next){ int y=e[i].v; if(vis[y]){ continue; } tot=0; dis[y]=e[i].w; getdis(y,x); for(int j=1;j<=tot;j++){ for(int k=1;k<=m;k++){ if(query[k]>=rev[j]){ pax[k]|=pd[query[k]-rev[j]]; } } } for(int j=1;j<=tot;j++){ pd[rev[j]]=1; q[++c]=rev[j]; } } for(int i=1;i<=c;i++){ pd[q[i]]=0; } } void solve(int x){ vis[x]=1,pd[0]=1; doit(x);//处理当x为根,所有点对的距离 for(int i=head[x];i;i=e[i].next){ int y=e[i].v; if(vis[y]){ continue; } //dp[0]=n; sum=siz[y];//设子树大小 root=0;//更新子树重心 getroot(y,x); solve(root); } } signed main(){ ios::sync_with_stdio(false); cin.tie(nullptr); cin>>n>>m; for(int i=1;i<=n-1;i++){ getroot(1,0); solve(root); for(int i=1;i<=m;i++){ if(pax[i]){ cout<<"AYE\n"; } else{ cout<<"NAY\n"; } } return 0; }
P4178 Tree
发现和上面的相似,只是变成了小于等于的个数,那就将长度排序后双指针求个数即可,但是可能会有同一颗子树内的点的距离算进去,所以要递归子树时容斥地减去子树内的点对。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· .NET10 - 预览版1新功能体验(一)