zoukankan      html  css  js  c++  java
  • 【ZJOI2018】历史

    【ZJOI2018】历史

    该来的总归还是会来的……

    题意:

    题目传送门

    题解:

    考虑把题意转化成一个更加科学一点的模型,发现这个崛起操作类似于(LCT)(Access)操作,继续分析一下,发现每次崛起的灾难度就是这次(Access)的轻重链的切换次数。那么题目就转化成了给出一个(Access)的顺序,让操作完之后的轻重链切换次数最大。

    接下来考虑什么时候会造成轻重链的切换。显然,对于一个点(x),如果(x)所引出的重链发生了改变,那么说明来自(x)的两个不同子树中的节点进行了(Access)操作。

    另一个容易发现的的结论就是每个点对于答案的贡献是独立的,所以我们可以单独计算每个点的贡献。我们记(Ans_x)表示(x)节点处轻重链切换次数的最大值,那么答案就是(sum_x Ans_x)

    接下来考虑如何计算一个点轻重链切换次数的最大值,我们记(A_v)表示(v(v in son_x))这个子树中的(Access)次数。我们实际上可以将问题的模型进行再次转化,对于一个点(x),我们把(x)子树中的所有(Access)操作看成一个小球,在(x)同一个孩子节点的子树下的球颜色一样,现在需要安排一个球的摆放顺序,使得相邻两个球颜色不同的数量最多。

    考虑一种贪心构造的方法,我们记(Mx_x)表示(x)的孩子节点中(A)最大的值,如果(Mx_x * 2 > A_x + 1),那么答案就是(2 * (A_x - Mx_x)),否则答案就是(A_x - 1)。这个是容易证明的,我们尝试把其他颜色的球放在颜色最多的球的间隔中,如果放的满,那么此时其余颜色的球最少为(Mx_x - 1),答案就是球的个数减一,即(A_x - 1),否则说明一些颜色最多的球会放在一起,那么答案就是其余颜色球的个数乘以(2),即(2 * (A_x - Mx_x))

    于是我们现在就有了一个(O(n))计算贡献的方法了。考虑如何优化。

    对于一个点(u),如果某个点(v)(A_v * 2 > A_u + 1),那么我们就记(u)(v)的边为重边,否则为轻边,这样我们的答案可以简单的计算了,如果(u)没有重边,那么答案就是(A_x - 1),否则就是(2 * (A_x - A_{heavyson}))

    我们发现,对于一次修改,影响到的就是(x)到根的一条链。对于经过的边,我们分情况讨论:

    如果经过的边是重边,由于每次加上的一定是一个正整数,所以原本是重边的边仍然是重边,并且答案不变,因为(A_x)(Mx_x)都变大了(w_i)

    如果经过的边是轻边,那么这次修改操作可能会让它变成重边,那么我们判断其是否会变成重边,然后重新计算一下答案即可。

    为什么这样复杂度是正确的呢?考虑每经过一条轻边,(A)的值至少会乘以(2),那么轻边的个数最多为(log)值域 级别的。所以每次修改只会修改(log)次。

    至于如何维护这棵树呢?一种方法是使用(LCT),稍微不同的是(Access)操作的时候不是将到根的所有边都设置成重边,而是按照上述的方法判断是否切换。然后复杂度分析仍然是按照(LCT)的分析,还是一样的。

    Code:

    #include <bits/stdc++.h>
    using namespace std;
    const int N = 4e5 + 50;
    typedef long long ll;
    
    int n, m, tot;
    ll a[N], res[N];
    ll ans;
    int heav[N], head[N];
    struct edge {
      int to, nxt;
    }E[N << 1];
    
    namespace LCT {
      struct node {
        int ch[2];
        int fa;
        ll sum, tag;
      }t[N << 1];
    #define ls(o) t[o].ch[0]
    #define rs(o) t[o].ch[1]
    #define pa(o) t[o].fa
      int Which(int o) { return rs(pa(o)) == o; }
      int Isroot(int o) { return ls(pa(o)) != o && rs(pa(o)) != o; }
      void Apply(int o, ll v) { t[o].sum += v; t[o].tag += v; }
      
      void Down(int o) {
        if(!o || !t[o].tag) return ;
        if(ls(o)) Apply(ls(o), t[o].tag);
        if(rs(o)) Apply(rs(o), t[o].tag);
        t[o].tag = 0;
      }
      
      void Rotate(int o) {
        int f = pa(o), ff = pa(f), c = Which(o);
        if(!Isroot(f)) t[ff].ch[Which(f)] = o; t[o].fa = ff;
        pa(t[o].ch[c ^ 1]) = f; t[f].ch[c] = t[o].ch[c ^ 1];
        t[o].ch[c ^ 1] = f; t[f].fa = o;
      }
      
      void TreeDown(int o) { if(!Isroot(o)) TreeDown(pa(o)); Down(o); }
      
      void Splay(int o) {
        TreeDown(o);
        for(; !Isroot(o); Rotate(o)) {
          if(!Isroot(pa(o))) Rotate( Which(o) ^ Which(pa(o)) ? o : pa(o));
        }
      }
    
    
      int Findroot(int o) {
        while(ls(o)) Down(o), o = ls(o);
        return o;
      }
    
      void Getans(int o) {
        ans -= res[o];
        res[o] = heav[o] ? (2ll * (t[o].sum - t[heav[o]].sum)) : (t[o].sum - 1);
        if(a[o] * 2 > t[o].sum + 1) res[o] = 2ll * (t[o].sum - a[o]);
        ans += res[o];
      }
      
      void Access(int o, ll v) {
        a[o] += v;
        for(int y = 0; o; y = o, o = pa(o)) {
          Splay(o);
          t[o].sum += v;
          if(ls(o)) Apply(ls(o), v);
          if(heav[o]) {
            TreeDown(heav[o]);
            if(t[heav[o]].sum * 2 <= t[o].sum + 1) heav[o] = 0, rs(o) = 0;
          }
          int v = Findroot(y);
          if(t[v].sum * 2 > t[o].sum + 1) heav[o] = v, rs(o) = v;
          Getans(o);
        }
      }
    }
    using namespace LCT;
    
    void Addedge(int u, int v) {
      E[++tot].to = v; E[tot].nxt = head[u]; head[u] = tot;
    }
    
    void Dfs(int o, int fa) {
      t[o].fa = fa; t[o].sum = a[o];
      ll Mx = -1, pos;
      for(int i = head[o]; ~i; i = E[i].nxt) {
        int to = E[i].to;
        if(to == fa) continue;
        Dfs(to, o);
        t[o].sum += t[to].sum;
        if(t[to].sum > Mx) { Mx = t[to].sum; pos = to; }
      }
      if(Mx * 2 > t[o].sum + 1) heav[o] = pos;
      Getans(o);
      rs(o) = heav[o];
    }
    
    int main() {
      memset(head, -1, sizeof head);
      scanf("%d%d", &n, &m);
      for(int i = 1; i <= n; i++) scanf("%lld", &a[i]);
      for(int i = 1, u, v; i < n; i++) {
        scanf("%d%d", &u, &v);
        Addedge(u, v); Addedge(v, u);
      }
      Dfs(1, 0);
      printf("%lld
    ", ans);
      for(int i = 1; i <= m; i++) {
        int x; ll y;
        scanf("%d%lld", &x, &y);
        Access(x, y);
        printf("%lld
    ", ans);
      }
      return 0;
    }
    
  • 相关阅读:
    AMQP 介绍
    JMS 介绍
    SpringCloud 入门实战(12)--Zipkin(2)--安装使用
    SpringCloud 入门实战(11)--Zipkin(1)--简介
    Kafka 入门实战(3)--SpringBoot 整合 Kafka
    跨域访问方法介绍(9)--nginx 反向代理
    跨域访问方法介绍(8)--使用 WebSocket
    个人网站建好啦!
    Spring Boot配置Slf4j + log4j2
    SpringBoot 解决跨域,3种解决方案
  • 原文地址:https://www.cnblogs.com/Apocrypha/p/10732346.html
Copyright © 2011-2022 走看看