[Codechef October Challenge 2014]Union on Tree(虚树+点分树)

Description

一个树,边都是单位长度。

  • 你有 Q Q 个询问,每个询问形如:

    • 假如在树上放m个警卫,第 i i 个警卫放在xi点,它可以看守到距离
      xi x i 点距离不超过 ri r i 的所有点。

    • 问这些警卫一起可以看到多少个点。
    • 每个询问都是独立的。

    • n50000 n ⩽ 50000 ,询问的警卫总数不超过 500000 500000
    • Solution

      考虑m=1的情况

      如果 m=1 m = 1 ,则意味着只有一名警卫。那么询问就等价于查询距离一个节点小于 range r a n g e 的节点数。
      这就成为了一道点分树的板子题,在点分树上的每个节点维护 A,B A , B 两个 vector v e c t o r , 分别表示节点 u u 在点分树上的子树信息,与在原树上的子树信息。

      Au,i:在点分树中,以u为根的点分树子树距离点分树根节点( u u )不超过i的节点数。

      Bu,i B u , i :在原树中,以u为根的点分树子树距离此子树在原树中的根( fau f a u 的对应儿子)节点( u u )不超过i的节点数。

      每次计算距离节点 u u 不超过r的节点数时的时候就暴力跳父节点,加上 A[rd] A [ r − d ] 减去对应子节点的 B[rd1] B [ r − d − 1 ]

      考虑一般的询问

      m500000 ∑ m ⩽ 500000 ,很容易想到建虚树。
      现在考虑一般的询问如何计算,首先更新每个在虚树上的节点能警卫到的范围(虚树中添加的lca节点以及在某些情况下, rb<radist(a,b) r b < r a − d i s t ( a , b ) ).
      更新后直接按前文所述累加每个点能警戒到的范围。

      但是这样有的节点被重复统计,考虑一条边 (u,v) ( u , v ) z z 是这条变的中点(即r[u]dist(u,z)=r[v]dist(v,z))。

      z z 节点有一个性质,跨过z后用 u u v优,否则用 z z 比用u优。

      所以这条边重复覆盖的部分就是 u u 跨过z警戒的范围, v v 跨过z警戒的范围。即 z z 能警戒的范围。所以减去z能警戒的范围即可。

      #include <bits/stdc++.h>
      using namespace std;
      
      const int maxn = 100005;
      struct edge {
          int to, next;
      }e[maxn * 2];
      int h[maxn], tot, n, m, q, p[maxn], r[maxn], ans;
      
      inline void add(int u, int v)
      {
          e[++tot] = (edge) {v, h[u]}; h[u] = tot;
          e[++tot] = (edge) {u, h[v]}; h[v] = tot;
      }
      
      inline int gi()
      {
          char c = getchar();
          while(c < '0' || c > '9') c = getchar();
          int sum = 0;
          while('0' <= c && c <= '9') sum = sum * 10 + c - 48, c = getchar();
          return sum;
      }
      
      //树链剖分求lca
      int siz[maxn], dep[maxn], son[maxn], dfn[maxn], Time, fa[maxn], g[maxn], order[maxn];
      inline void dfs1(int u) //链剖dfs1
      {
          dep[u] = dep[fa[u]] + 1;
          siz[u] = 1;
          for(int i = h[u], v; i; i = e[i].next)
              if((v = e[i].to) != fa[u]) {
                  fa[v] = u;
                  dfs1(v); siz[u] += siz[v];
                  if(siz[v] > siz[son[u]]) son[u] = v;
              }
      }
      
      inline void dfs2(int u) //链剖dfs2
      {
          order[dfn[u] = ++Time] = u;
          if(son[u]) {
              g[son[u]] = g[u]; dfs2(son[u]);
              for(int i = h[u], v; i; i = e[i].next)
                  if((v = e[i].to) != fa[u] && v != son[u]) {
                      g[v] = v; dfs2(v);
                  }
          }
      }
      
      inline int lca(int u, int v)
      {
          while(g[u] != g[v]) {
              if(dep[g[u]] > dep[g[v]]) u = fa[g[u]];
              else v = fa[g[v]];
          }
          return dep[u] < dep[v] ? u : v;
      }
      
      //构建点分树
      vector<int> A[maxn], B[maxn]; //A:点分树子树信息,B:原树子树信息
      int rt[maxn][20], d[maxn][20], len[maxn];
      
      int cent, Min, sum;
      bool vis[maxn];
      void getroot(int u, int fa) //找重心
      {
          int Mxsz = 0; siz[u] = 1;
          for(int i = h[u], v; i; i = e[i].next)
              if((v = e[i].to) != fa && !vis[v]) {
                  getroot(v, u); siz[u] += siz[v];
                  Mxsz = max(Mxsz, siz[v]);
              }
          Mxsz = max(Mxsz, sum - siz[u]);
          if(Mxsz < Min) Min = Mxsz, cent = u;
      }
      
      void dfs3(int u,int fa, int dis, int root, vector<int> &v) //统计子树信息
      {
          rt[u][len[u]] = root;
          d[u][len[u]++] = dis;
          if(dis == (int)v.size()) v.push_back(u <= n); else v[dis] += u <= n;
          for(int i = h[u]; i; i = e[i].next)
              if(e[i].to != fa && !vis[e[i].to])
                  dfs3(e[i].to, u, dis + 1, root, v);
      }
      
      void dfs4(int u, int fa, int dis, vector<int> &v)
      {
          if(dis == (int)v.size()) v.push_back(u <= n); else v[dis] += u <= n;
          for(int i = h[u]; i; i = e[i].next)
              if(e[i].to != fa && !vis[e[i].to]) 
                  dfs4(e[i].to, u, dis + 1, v);
      }
      
      void solve(int u, vector<int> &v) //构建点分树主过程
      {
          Min = n << 1; getroot(u, 0);
          vis[u = cent] = true;
          B[u] = v;
          dfs3(u, 0, 0, u, A[u]);
          for(int len = A[u].size(), i = 1; i < len; ++i) A[u][i] += A[u][i - 1];
          for(int i = h[u], v; i; i = e[i].next)
              if(!vis[v = e[i].to]) {
                  vector<int> t(1, 0);
                  dfs4(v, 0, 1, t);
                  for(int len = t.size(), i = 1; i < len; ++i) t[i] += t[i - 1];
                  sum = siz[v]; solve(v, t);
              } 
      }
      
      //建虚树
      int stk[maxn], top, pre[maxn];
      
      inline bool cmp1(const int &a, const int &b) {return dfn[a] > dfn[b];}
      inline bool cmp2(const int &a, const int &b) {return dfn[a] < dfn[b];}
      
      void build_tree()
      {
          sort(p + 1, p + m + 1, cmp1);
          stk[top = 1] = p[m]; 
          for(int i = m - 1, x; i >= 1; --i) {
              int u = lca(p[i], stk[top]);
              for(x = 0; dfn[stk[top]] > dfn[u]; x = stk[top--])
                  if(x) pre[x] = stk[top];
              if(stk[top] != u) stk[++top] = p[++m] = u, r[u] = -1;
              if(x) pre[x] = stk[top];
              stk[++top] = p[i];
          }
          --top;
          while(top) pre[stk[top + 1]] = stk[top], --top;
          sort(p + 1, p + m + 1, cmp2);
      }
      
      inline int calc(int u, int dis) //计算距离u不超过dis的节点数
      {
          int s = 0;
          for(int i = 0; i < len[u]; ++i) {
              if(i && dis >= d[u][i - 1]) s -= B[rt[u][i]][min(dis - d[u][i - 1], (int)B[rt[u][i]].size() - 1)];
              if(dis >= d[u][i]) s += A[rt[u][i]][min(dis - d[u][i], (int)A[rt[u][i]].size() - 1)];
          }
          return s;
      }
      
      inline int jump(int u, int d)
      {
          while(dep[g[u]] > d) u = fa[g[u]];
          return order[dfn[u] - (dep[u] - d)];
      }
      
      int main()
      {
          freopen("tree.in", "r", stdin);
          freopen("tree.out", "w", stdout);
      
          n = gi();
          for(int i = 1; i < n; ++i) {
              add(gi(), n + i); add(gi(), n + i);
          }
          sum = 2 * n - 1;
      
          dfs1(1);
          Time = 0; g[1] = 1; dfs2(1);
          vector<int> v; solve(1, v);
      
          q = gi();
          for(int i = 1; i <= q; ++i) {
              m = gi();
              for(int i = 1; i <= m; ++i) p[i] = gi(), r[p[i]] = gi() << 1;
              build_tree();
              for(int i = m; i > 1; --i) 
                  r[pre[p[i]]] = max(r[pre[p[i]]], r[p[i]] - (dep[p[i]] - dep[pre[p[i]]]));
              for(int i = 2; i <= m; ++i)
                  r[p[i]] = max(r[p[i]], r[pre[p[i]]] - (dep[p[i]] - dep[pre[p[i]]]));
              ans = 0;
              for(int i = 1; i <= m; ++i) ans += calc(p[i], r[p[i]]);
              for(int i = 2, k; i <= m; ++i) {
                  k = jump(p[i], (r[pre[p[i]]] - r[p[i]] + dep[p[i]] + dep[pre[p[i]]]) >> 1);
                  List(k, r[p[i]] - (dep[p[i]] - dep[k]));
                  ans -= calc(k, r[p[i]] - (dep[p[i]] - dep[k]));
              }
              printf("%d\n", ans);
          }
          return 0;
      }
      
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值