淀粉质学习笔记
什么是淀粉质点分治:
看到分治这两个字是不是特别熟悉?
分治算法有很多,点分治就是其中之一。
数列上的分治,就是把数列分成[1,mid][mid+1,n]来处理,不能分为像[1,1][2,n]这样,这样的话分治法就会退化为暴露枚举。
树上的点分治也是基于将树二等分的思想,主要用于处理树上路径问题。
点分治的思想(重心分解)
1.树的重心定义为删去该重心
点分治的时间复杂度分析:
首先每次寻找重心递归处理,使得每次处理规模减半,所以时间复杂度为
点分治的实现:
例题1:P4178 Tree
题意:求距离小于等于k的点对数量
分析:
先寻找重心,执行一次深度优先遍历统计子树的节点数,找不超过
然后统计每个子树节点对于重心的距离,存入
执行一次排序,从小到大,方便于使用双指针扫描法快速统计答案
最终答案加等于包含
然后减去重复统计的就是
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int maxn=10005;
int cnt,n,k,ans,head[maxn];
int root,S,size[maxn],f[maxn],d[maxn],dep[maxn];
bool vis[maxn];
struct edge
{
int to,next,w;
}edge[maxn*2];
void add(int u,int v,int w)
{
edge[++cnt].to=v;
edge[cnt].w=w;
edge[cnt].next=head[u];
head[u]=cnt;
}
void getroot(int u,int fa)//获取重心
{
size[u]=1;
f[u]=0;//删除u后,最大子树的大小
for(int i=head[u];i;i=edge[i].next)
{
int v=edge[i].to;
if(v!=fa&&!vis[v])
{
getroot(v,u);
size[u]+=size[v];
f[u]=max(f[u],size[v]);
}
}
f[u]=max(f[u],S-size[u]);//S为当前子树总结点数
if(f[u]<f[root])
root=u;
}
void getdep(int u,int fa)//获取距离
{
dep[++dep[0]]=d[u];//保存距离数组
for(int i=head[u];i;i=edge[i].next)
{
int v=edge[i].to;
if(v!=fa&&!vis[v])
{
d[v]=d[u]+edge[i].w;
getdep(v,u);
}
}
}
int getsum(int u,int dis) //获取u的子树中满足个数
{
d[u]=dis;
dep[0]=0;
getdep(u,0);
sort(dep+1,dep+1+dep[0]);
int L=1,R=dep[0],sum=0;
while(L<R)
if(dep[L]+dep[R]<=k)
sum+=R-L,L++;
else
R--;
return sum;
}
void solve(int u) //获取答案
{
vis[u]=true;
ans+=getsum(u,0);
for(int i=head[u];i;i=edge[i].next)
{
int v=edge[i].to;
if(!vis[v])
{
ans-=getsum(v,edge[i].w);//减去重复
root=0;
S=size[v];
getroot(v,u);
solve(root);
}
}
}
int main()
{
f[0]=0x7fffffff;//初始化树根
while(scanf("%d%d",&n,&k),n+k)
{
memset(vis,0,sizeof(vis));
memset(head,0,sizeof(head));
cnt=0;ans=0;
for(int i=1;i<=n-1;i++)
{
int x,y,z;
scanf("%d%d%d",&x,&y,&z);
add(x,y,z);
add(y,x,z);
}
root=0;
S=n;
getroot(1,0);
solve(root);
printf("%d\n",ans);
}
return 0;
}
例题2:
鸽子