zoukankan      html  css  js  c++  java
  • 「学习笔记」点分治

    「学习笔记」点分治

    引子

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

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

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

  • 相关阅读:
    ZOJ Problem Set–2417 Lowest Bit
    ZOJ Problem Set–1402 Magnificent Meatballs
    ZOJ Problem Set–1292 Integer Inquiry
    ZOJ Problem Set–1109 Language of FatMouse
    ZOJ Problem Set–1295 Reverse Text
    ZOJ Problem Set–1712 Skew Binary
    ZOJ Problem Set–1151 Word Reversal
    ZOJ Problem Set–1494 Climbing Worm
    ZOJ Problem Set–1251 Box of Bricks
    ZOJ Problem Set–1205 Martian Addition
  • 原文地址:https://www.cnblogs.com/BruceW/p/12118933.html
Copyright © 2011-2022 走看看