• 「学习笔记」点分治


    「学习笔记」点分治

    引子

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

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

    比如, 树上是否存在长为 (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
    ",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
    ",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
    ",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

  • 相关阅读:
    李永乐,皇帝的新衣背后,共有知识和公共知识
    汇率原理
    mybatis pageHelper 分页插件使用
    oracle中的exists 和not exists 用法详解
    Webservice入门简单实例
    java-可逆加密算法
    idea 卡顿问题
    idea svn操作
    HttpServletrequest 与HttpServletResponse总结
    Spring boot中应用jpa jpa用法
  • 原文地址:https://www.cnblogs.com/BruceW/p/12118933.html
Copyright © 2020-2023  润新知