zoukankan      html  css  js  c++  java
  • 省队集训Ⅱ-Day7

    Day7

    又是只整了一道题的一天

    Tree

    给一棵点带权的树, 分成两个集合 (A)(B), (A) 的权值是集合中 (i)(j) 的祖先, 且 (V_i > V_j) 的无序点对数加 (i)(j) 无直系关系 (不存在一个点是另一个点的祖先) 的无序点对数加集合中点的深度之和 (根深为 (0)), (B) 的权值是集合中 (i)(j) 的祖先, 满足 (V_i < V_j) 的无需点对数.

    考场上以为写出正解, 每个节点开了三棵可持久化线段树合并, 最后不会维护, 弃了正解.

    然后发现貌似每个答案都是由上一个答案的基础上再从 (A) 中拿一个点放入 (B) 的, 事实证明赌对了, 可以贪心, 写了个 (n^2) 的暴力, 痛苦收场:

    unsigned n, m, Vmax(0), A, B, Standard;
    long long Ans(0), Distur(0), AnsDou(0);
    char flg(0);
    struct Edge;
    struct Node {
      char Deleted;
      Edge *Fst;
      Node *Fa;
      unsigned Val, Dep, Size, Contri, Ace, DeceRo, DeceLe;
      inline const char operator<(const Node &x) const{
        return this->Contri < x.Contri;
      }
    }N[500005];
    struct Edge {
      Node *To;
      Edge *Nxt;
    }E[1000005], *CntE(E);
    priority_queue<Node> Q;
    inline void Link(Node *x, Node *y) {
      (++CntE)->Nxt = x->Fst;
      x->Fst = CntE;
      CntE->To = y;
    } 
    void DFS(Node *x) {
      register Edge *Sid(x->Fst);
      x->Size = 1;
      while (Sid) {
        if(Sid->To != x->Fa) {
          Sid->To->Dep = x->Dep + 1;
          Sid->To->Fa = x;
          DFS(Sid->To);
          x->Size += Sid->To->Size;
        }
        Sid = Sid->Nxt;
      }
      return;
    }
    unsigned DFS1(Node *x) {
      register Edge *Sid(x->Fst);
      register unsigned TmpDe((x->Val < Standard) ? 1 : 0);
      while (Sid) {
        if(Sid->To != x->Fa)
          TmpDe += DFS1(Sid->To);
        Sid = Sid->Nxt;
      }
      return TmpDe;
    }
    int main() {
      n = RD();
      for (register unsigned i(1); i <= n; ++i)
        N[i].Val = RD(), Vmax = (Vmax < N[i].Val) ? N[i].Val : Vmax;
      for (register unsigned i(1); i < n; ++i) {
        A = RD(), B = RD();
        Link(N + A, N + B);
        Link(N + B, N + A);
      }
      A = N[1].Val, N[1].Dep = 0;
      DFS(N + 1);
      for (register unsigned i(1); i <= n; ++i) {
        Standard = N[i].Val;
        Ans += (N[i].DeceRo = DFS1(N + i));
        AnsDou += n + N[i].Dep - N[i].Size;
      }
      Ans += (AnsDou >> 1);
      printf("%lld
    ", Ans);
      for (register unsigned i(1); i <= n; ++i) { // i th
        register Node *Choice, *now;
        register long long Con;
        Distur = -0x3f3f3f3f3f3f3f3f;
        for (register unsigned j(1); j <= n; ++j) { // Del j
          if(N[j].Deleted) {continue;}
          now = N[j].Fa;
          Con = (long long)n - i + 1 - N[j].Size + N[j].DeceRo - N[j].DeceLe;
          while (now) {
            if(!now->Deleted) if (now->Val > N[j].Val) ++Con;
            else {++Con; if (now->Val < N[j].Val) --Con;}
            now = now->Fa;
          }
          if(Con > Distur) Distur = Con, Choice = N + j;
        }
        Choice->Deleted = 1;
        now = Choice->Fa;
        while (now) {
          --(now->Size);
          if (now->Val < Choice->Val) ++(now->DeceLe);
          if (now->Val > Choice->Val) --(now->DeceRo);
          now = now->Fa;
        }
        Ans -= Distur;
        printf("%lld
    ", Ans);
      }
      return 0;
    }
    

    下面是正解:

    首先发现这些 (B) 中计入权值的点对数, 在所有点对中的补就是 (A) 中计入权值的点数.

    先讨论节点权值互不相同的情况, 考虑一个点 (x)(A) 放到 (B) 中, 它对答案的贡献是:

    [sum_{i in B}[i 是 x 的祖先, V_i < V_x] + sum_{i in B}[x 是 i 的祖先, V_i > V_x] - sum_{i in A}[i 是 x 的祖先, V_i > V_x] - sum_{i in A}[x 是 i 的祖先, V_x > V_i] - sum_{i in A}[i 不是 x 的祖先, x 不是 i 的祖先] - Dep_i ]

    因为

    [|A| - 1 = sum_{i in A}[i 是 x 的祖先, V_i > V_x] + sum_{i in A}[x 是 i 的祖先, V_x > V_i] + sum_{i in A}[i 是 x 的祖先, V_i < V_x] + sum_{i in A}[x 是 i 的祖先, V_x < V_i] + sum_{i in A}[i 不是 x 的祖先, x 不是 i 的祖先] ]

    所以贡献变成了:

    [sum_{i in B}[i 是 x 的祖先, V_i < V_x] + sum_{i in B}[x 是 i 的祖先, V_i > V_x] - Dep_i - |A| + sum_{i in A}[i 是 x 的祖先, V_i < V_x] + sum_{i in A}[x 是 i 的祖先, V_x < V_i] ]

    因为 (A), (B) 关于全部节点互补, 所以转化为:

    [sum_{i}[i 是 x 的祖先, V_i < V_x] + sum_{i}[x 是 i 的祖先, V_i > V_x] - Dep_i - |A| + 1 ]

    (|A|) 和选哪个点无关, 而 (displaystyle{sum_{i}[i 是 x 的祖先, V_i < V_x] + sum_{i}[x 是 i 的祖先, V_i > V_x] - Dep_i})(A), (B) 的元素无关, 所以可以预处理, 然后贪心选择这个值最小的.

    接下来考虑有权值相同的情况. 如果 (V_i = V_j), 但是 (i), (j) 互不是对方的祖先, 这时它们的权值相对大小对答案无影响. 不失一般性, 不妨设 (i)(j) 的祖先, 这时 (sum_{k}[k 是 i 的祖先, V_k < V_i]) 一定不大于 (sum_{k}[k 是 j 的祖先, V_k < V_j]), (sum_{k}[i 是 k 的祖先, V_k > V_i]) 一定不小于 (sum_{k}[j 是 k 的祖先, V_k > V_j]). 而造成 (sum_{k}[k 是 j 的祖先, V_k < V_j] - sum_{k}[k 是 i 的祖先, V_k < V_i]) 差值的, 一定是 (i), (j) 中间的点, 数量不超过 (Dep_j - Dep_i - 1) 个, 所以, (i) 的贡献一定不如 (j) 的小, 所以选 (j).

    考虑权值相同, 则:

    [|A| - 1 = sum_{i in A}[i 是 x 的祖先, V_i > V_x] + sum_{i in A}[x 是 i 的祖先, V_x > V_i] + sum_{i in A}[i 是 x 的祖先, V_i < V_x] + sum_{i in A}[x 是 i 的祖先, V_x < V_i] + sum_{i in A}[i 是 x 的祖先, V_i = V_x] + sum_{i in A}[x 是 i 的祖先, V_x = V_i] + sum_{i in A}[i 不是 x 的祖先, x 不是 i 的祖先] ]

    贡献值变成:

    [sum_{i}[i 是 x 的祖先, V_i < V_x] + sum_{i}[x 是 i 的祖先, V_i > V_x] + sum_{i in A}[i 是 x 的祖先, V_i = V_x] + sum_{i in A}[x 是 i 的祖先, V_x = V_i] - Dep_i - |A| + 1 ]

    但是前面说了, 权值相同的祖先和后代, 先选后代更优, 也就是说, 在权值相等时, 一定是先选后代, 再选祖先, 所以不能存在 (i)(j) 的祖先, (V_i = V_j), 且 (i in B), (j in A) 的情况.

    所以对于 (x in A), 所有的 (V_i = V_x), 只要 (i)(x) 的祖先, 一定有 (i in A);

    对于 (x in B), 所有的 (V_i = V_x), 只要 (x)(i) 的祖先, 一定有 (i in B)

    所以 (x) 放入 (B) 的时候, 有:

    [sum_{i in A}[x 是 i 的祖先, V_x = V_i] = 0\ sum_{i in A}[i 是 x 的祖先, V_i = V_x] = sum_{i}[i 是 x 的祖先, V_i = V_x] ]

    所以 (x) 的贡献值就是:

    [sum_{i}[i 是 x 的祖先, V_i < V_x] + sum_{i}[x 是 i 的祖先, V_i > V_x] + sum_{i}[i 是 x 的祖先, V_i = V_x] - Dep_i - |A| + 1\ = sum_{i}[i 是 x 的祖先, V_i leq V_x] + sum_{i}[x 是 i 的祖先, V_i > V_x] - Dep_i - |A| + 1 ]

    上代码:

    #define Lowbit(x) ((x)&(-(x)))
    unsigned n, m, Vmax(0), A, B, FaTr[500005], DeTr[500005];
    int Con[500005];
    long long Ans(0);
    char flg(0);
    struct Edge;
    struct Node {
      char Deleted;Edge *Fst;Node *Fa;
      unsigned Val, Dep, Size, Ace, Dece;
      int Contri;
      inline const char operator<(const Node &x) const{return this->Contri < x.Contri;}
    }N[500005];
    struct Edge {Node *To; Edge *Nxt;}E[1000005], *CntE(E);
    priority_queue<Node> Q;
    inline void Link(Node *x, Node *y) {(++CntE)->Nxt = x->Fst, x->Fst = CntE, CntE->To = y;}
    inline void FaAdd(unsigned Pos) {while (Pos <= Vmax) ++FaTr[Pos], Pos += Lowbit(Pos);}
    inline void FaMinu(unsigned Pos) {while (Pos <= Vmax) --FaTr[Pos], Pos += Lowbit(Pos);}
    inline int FaQry(unsigned Pos) {
      register int TmpA(0);
      while (Pos) TmpA += FaTr[Pos], Pos -= Lowbit(Pos);
      return TmpA;
    }
    inline void DeAdd(unsigned Pos) {while (Pos <= Vmax) ++DeTr[Pos], Pos += Lowbit(Pos);}
    inline void DeMinu(unsigned Pos) {while (Pos <= Vmax) --DeTr[Pos], Pos += Lowbit(Pos);}
    inline int DeQry(unsigned Pos) {
      register int TmpA(0);
      while (Pos) TmpA += DeTr[Pos], Pos -= Lowbit(Pos);
      return TmpA;
    }
    void DFS(Node *x) {
      register Edge *Sid(x->Fst);
      x->Size = 1, x->Contri = FaQry(x->Val), FaAdd(x->Val), DeAdd(x->Val);
      Ans += ((FaQry(Vmax) - FaQry(x->Val)) << 1);
      register unsigned TmpSum(DeQry(Vmax) - DeQry(x->Val));
      while (Sid) {
        if(Sid->To != x->Fa) {
          Sid->To->Dep = x->Dep + 1, Sid->To->Fa = x;
          DFS(Sid->To);
          x->Size += Sid->To->Size, Ans += n + Sid->To->Dep - Sid->To->Size;
        }
        Sid = Sid->Nxt;
      }
      x->Contri = x->Contri + (DeQry(Vmax) - DeQry(x->Val) - TmpSum) - x->Dep;
      FaMinu(x->Val);
      return;
    }
    int main() {
      n = RD();
      for (register unsigned i(1); i <= n; ++i)
        N[i].Val = RD(), Vmax = (Vmax < N[i].Val) ? N[i].Val : Vmax;
      for (register unsigned i(1); i < n; ++i)
        A = RD(), B = RD(), Link(N + A, N + B), Link(N + B, N + A);
      A = N[1].Val, N[1].Dep = 0;
      DFS(N + 1);
      Ans = Ans >> 1;
      printf("%lld
    ", Ans);
      for (register unsigned i(1); i <= n; ++i) Con[i] = N[i].Contri;
      sort(Con + 1, Con + n + 1);
      for (register int i(1); i <= n; ++i) Ans += Con[i], Ans -= (int)n - i, printf("%lld
    ", Ans);
      return 0;
    }
    
  • 相关阅读:
    从C,C++,JAVA和C#看String库的发展(一)----C语言和C++篇
    Intent----android中的伟大邮差
    一步一步掌握线程机制(五)---等待与通知机制
    一步一步掌握线程机制(四)---同步方法和同步块
    一步一步掌握线程机制(三)---synchronized和volatile的使用
    利用单例模式解决全局访问问题
    一步一步掌握java的线程机制(二)----Thread的生命周期
    一步一步掌握java的线程机制(一)----创建线程
    如何快速学会android的四大基础----Service篇
    前端工程师必备实用网站
  • 原文地址:https://www.cnblogs.com/Wild-Donkey/p/14856653.html
Copyright © 2011-2022 走看看