点分治 学习笔记

引子

点分治, 其实应该叫 "树上点分治".

主要用于解决 "树上路径问题" (我乱起的名字).

比如, 树上是否存在长为 \(k\) 的路径, 树上长小于 \(k\) 的路径有多少条等等.

点分治可以概括为 : 分值 + 重心 + 桶 (就目前我做过的几道题来说都是这个套路)

算法过程

我们就直接针对一道题来吧.

询问树上距离为 \(k\) 的点对是否存在

换句话说, 就是树上是否存在长度为 \(k\) 的路径.

首先, 选一个根节点, 然后树上的路径就被分成了两类,

  1. 经过根节点.
  2. 不经过根节点.

考虑怎么处理第一类路径.

我们设 \(dis[u]\) 为点 \(u\) 到根节点的距离,

建一个桶 \(b[i]\), 表示是否存在 \(dis[u] = i\) 的节点.

然后对每一棵子树都 \(dfs\) 两遍.

第一遍查看桶中是否存在 \(dis = k-dis[u]\) 的点, 即 \(b[k-dis[u]]\) 是否为 \(1\).

第二遍把点放进桶中, 即 \(b[dis[u]]=1\).

第一类路径就这样处理完了, 时间复杂度为 \(O(n)\).

而对于第二类路径, 我们可以把它看做经过了另外一个 "根节点" 的第一类路径, 然后就可以递归完成了.

若递归层数为 \(T\), 总时间复杂度就为 \(O(Tn)\).

那我们怎么使 \(T\) 尽量小呢?

答案是------以重心作为根节点.

这还是比较好理解的, 因为重心会将整棵树分成若干个 \(size \le \frac{n}{2}\) 的子树, 所以 \(T\) 就是 \(\log n\) 级别的.

这样, 总复杂度就为 \(O(n \log n)\) 了.

还有一个小问题, 就是我们在处理第一类路径中所要使用的桶, 如果每次递归都要全部清空会浪费很多时间.

因此, 我们在修改桶的时候, 可以用一个队列把修改过的位置记下来, 最后清空的时候只需要把这些位置归零就行了, 这样就保证了每一层递归的修改操作的时间复杂度为 \(O(n)\).

代码

#include<bits/stdc++.h>
using namespace std;
const int _=1e4+7;
const int __=1e7+7;
const int inf=0x3f3f3f3f;
bool st;
int n,m,maxk=10000000,sz[_],dis[_],rt,minx=inf,q[_],top,qes[100+7];
int lst[_],nxt[2*_],to[2*_],len[2*_],tot;
bool vis[_],b[__],ans[100+7];
bool en;
void add(int x,int y,int w){ nxt[++tot]=lst[x]; to[tot]=y; len[tot]=w; lst[x]=tot; }
void read(){
  cin>>n>>m;
  int x,y,w;
  for(int i=1;i<n;i++){
    scanf("%d%d%d",&x,&y,&w);
    add(x,y,w);
    add(y,x,w);
  }
  for(int i=1;i<=m;i++) scanf("%d",&qes[i]);
}
void pre(int u,int fa){
  sz[u]=1;
  for(int i=lst[u];i;i=nxt[i]){
    int v=to[i];
    if(v==fa||vis[v]) continue;
    pre(v,u);
    sz[u]+=sz[v];
  }
}
void g_rt(int u,int fa,int sum){
  int maxn=sum-sz[u];
  for(int i=lst[u];i;i=nxt[i]){
    int v=to[i];
    if(v==fa||vis[v]) continue;
    pre(v,u);
    maxn=max(maxn,sz[v]);
  }
  if(maxn<minx){ minx=maxn; rt=u; }
}
void cnt(int u,int fa){
  //  printf("%d: %d\n",u,dis[u]);
  for(int i=1;i<=m;i++)
    if(qes[i]>=dis[u]&&b[qes[i]-dis[u]]) ans[i]=1;
  for(int i=lst[u];i;i=nxt[i]){
    int v=to[i];
    if(v==fa||vis[v]) continue;
    dis[v]=dis[u]+len[i];
    cnt(v,u);
  }
}
void mrk(int u,int fa){
  if(dis[u]>maxk) return;
  if(!b[dis[u]]){ q[++top]=dis[u]; b[dis[u]]=1; }
  for(int i=lst[u];i;i=nxt[i]){
    int v=to[i];
    if(v==fa||vis[v]) continue;
    mrk(v,u);
  }
}
void calc(int u){
  for(int i=lst[u];i;i=nxt[i]){
    int v=to[i];
    if(vis[v]) continue;
    dis[v]=len[i];
    //    printf(" %d: %d\n",v,dis[v]);
    cnt(v,0); mrk(v,0);
  }
  for(int i=1;i<=top;i++) b[q[i]]=0;
  top=0;
}
void run(int u,int lstrt){
  minx=inf;
  pre(u,0);
  g_rt(u,0,sz[u]);
  vis[rt]=1;
  //  printf("rt: %d\n",rt);
  u=rt;
  for(int i=lst[u];i;i=nxt[i]){
    int v=to[i];
    if(vis[v]) continue;
    run(v,u);
  }
  calc(u);
  vis[u]=0;
}
int main(){
  //freopen("template.in","r",stdin);
  //freopen("template.out","w",stdout);
  read();
  b[0]=1;
  run(1,0);
  for(int i=1;i<=m;i++) puts(ans[i] ?"AYE" :"NAY");
  return 0;
}

题目

【模板】点分治

P4178 Tree

[IOI2011]Race

参考资料

点分治略解 by Dispwnl

猜你喜欢

转载自www.cnblogs.com/brucew-07/p/12118933.html
今日推荐