uoj418 - 三角形 题解

「01 on tree trick」与「monster hunter trick」的结合,太牛逼了。

显然激活一个点之后,就可以立刻把它的儿子全部回收。容易想到,激活一个点的过程就和 monster 是等价的:先花 \(a_x\),然后返还 \(\sum\limits_{y\in son_x}a_y\)。除此之外,还有先打完所有儿子才能打 \(x\) 的拓扑关系限制。相当于就是问,对每个 \(x\),子树内最优的一种打怪顺序(需要是拓扑序)。

稍微转化一下,一个点被那么多儿子控制有点麻烦,不妨把序列倒过来,相当于是每个点只被父亲控制,而 monster 的合并与比较也要 swap 过来,问题不大。

首先是一个引理,关于一个序列是最优打怪顺序的必要条件:不能存在相邻的两个点,他们是逆序的(指 monster 比大小)且无拓扑关系。因为这样就可以交换来变得更优。但不一定会严格更优,这时候调整法的证明一定要注意附加证明调整一定在有限步内结束。对于该情形其实挺显然的,因为每次交换相邻逆序对都会使得全局逆序对数严格减小。。。

那么这种「树上最优拓扑序」又是老生常谈了。直接每次找最小的能选的点是经典的错误贪心。正确的应该是这样的:容易证明一个事实,对于全局最小点,当某个时刻它第一次成为候选点时,一定立刻选它。不然显然就一定有相邻无拓扑序逆序对啦噜。那么根据 01 on tree 的做法,此时应该合并它与它的父亲,用并查集维护。如何表示合并后的信息?洛谷「排列」那题是记录 cnt 和 sum,而这题则更为方便:monster 本身就是可合并的信息!连续打两个 monster 可以合并成等效于打一个 monster,具体如何合并就不需要多说了吧(?)

这样的话,如果只要求某个点的答案,就已经做完了。关键他要你求所有点对应的子树内的答案。这可怎么办呢?任意时刻,树上是一堆连通块,当一个连通块合并到其父亲连通块时,我们用该连通块最浅点来代表它。那么显然,每个点只会恰好代表一次合并,同时记录每次合并前的 monster 值。先用 set 模拟一便全局的合并过程,然后考虑某棵子树 \(x\),这时全局的 \(n-1\) 次合并操作对它而言分三种:

  1. 代表点在子树外或为 \(x\):那么没有任何影响,可以直接过滤。
  2. 代表点在子树内但不为 \(x\):照常进行。这时又分为两类:
    1. 它直接合并到 \(x\) 所在连通块:那么直接令 \(ans=ans+m\),其中 \(ans\) 是维护的答案 monster,初始值为 \(x\) 处的 monster,\(m\) 为该次合并前的 monster。最终 \(ans\) 就是答案。
    2. 否:那相当于没有影响,因为影响都集中体现在直接合并到 \(x\) 连通块的了。

那么容易发现,\(x\) 的答案就是将所有类别 2.1 的 monster 们按照全局模拟得到的顺序加起来。现在想知道一个点 \(x\),它能作为 2.1 影响到哪些点:显然是 \(fa_x\to up_x\) 这条链,其中 \(up_x\)\(x\) 合并至父亲连通块时父亲连通块的最浅点。那么就树上差分一下,使用线段树合并整理所有子树的信息和。复杂度 \(\mathrm O(n\log n)\)

code
constexpr int N = 2e5 + 10;

int n;
int fa[N]; vi son[N];
int a[N];

struct monster {
  int a, b;
  monster(int a = 0, int b = 0) : a(a), b(b) {}
  bool operator<(const monster &g) const {
    if((g.a <= g.b) != (a <= b)) return g.a <= g.b;
    if(g.a <= g.b) return g.a < a;
    else return g.b > b;
  }
  monster operator+(const monster &g) const {
    int least = max(g.a, g.a - g.b + a);
    return monster(least, least - (g.a - g.b + a - b));
  }
} o[N], ms[N];

struct ufset {
  int fa[N];
  int root(int x) { return fa[x] ? fa[x] = root(fa[x]) : x; }
} ufs;
int emperor[N], up[N];

struct segtree {
  int sz;
  struct node {
    int ls, rs, cnt; monster m;
    node(int ls = 0, int rs = 0, int cnt = 0, monster m = monster()) : ls(ls), rs(rs), cnt(cnt), m(m) {}
    #define ls(p) nd[p].ls
    #define rs(p) nd[p].rs
    #define cnt(p) nd[p].cnt
    #define m(p) nd[p].m
  } nd[N * 50];
  void sprup(int p) {
    cnt(p) = cnt(ls(p)) + cnt(rs(p));
    m(p) = m(ls(p)) + m(rs(p));
  }
  void add(int x, int v, int p, int tl = 1, int tr = n - 1) {
    if(tl == tr) return cnt(p) += v, m(p) = cnt(p) ? ms[emperor[x]] : monster(), void();
    int mid = tl + tr >> 1;
    if(x <= mid) {
      if(!ls(p)) ls(p) = ++sz;
      add(x, v, ls(p), tl, mid);
    } else {
      if(!rs(p)) rs(p) = ++sz;
      add(x, v, rs(p), mid + 1, tr);
    }
    sprup(p);
  }
  int mrg(int p, int q, int tl = 1, int tr = n - 1) {
    if(!p || !q) return p | q;
    if(tl == tr) return cnt(p) += cnt(q), m(p) = cnt(p) ? ms[emperor[tl]] : monster(), p;
    int mid = tl + tr >> 1;
    ls(p) = mrg(ls(p), ls(q), tl, mid);
    rs(p) = mrg(rs(p), rs(q), mid + 1, tr);
    return sprup(p), p;
  }
} sgt;

vi ad[N], de[N];
int ans[N];
int dfs(int x = 1) {
  int p = ++sgt.sz;
  for(int v : ad[x]) sgt.add(v, 1, p);
  for(int v : de[x]) sgt.add(v, -1, p);
  for(int y : son[x]) {
    int q = dfs(y);
    p = sgt.mrg(p, q);
  }
  // cout << x << ": " << sgt.m(p).a << " " << sgt.m(p).b << "\n";
  ans[x] = (o[x] + sgt.m(p)).a;
  return p;
}

void mian() {
  read();
  n = read();
  REP(i, 2, n) fa[i] = read(), son[fa[i]].pb(i);
  REP(i, 1, n) a[i] = read();
  REP(x, 1, n) {
    int sum = 0;
    for(int y : son[x]) sum += a[y];
    o[x] = ms[x] = monster(a[x], sum);
  }
  set<tuple<monster, int>> q;
  REP(i, 2, n) q.insert(mt(ms[i], i));
  REP(i, 1, n - 1) {
    int x = Y(*q.begin()); q.erase(q.begin());
    emperor[i] = x;
    int y = ufs.root(fa[x]);
    up[x] = y;
    if(y != 1) {
      q.erase(mt(ms[y], y));
      ms[y] = ms[y] + ms[x];
      q.insert(mt(ms[y], y));
    }
    ufs.fa[x] = y;
  }
  REP(i, 1, n - 1) {
    int x = emperor[i];
    ad[fa[x]].pb(i), de[fa[up[x]]].pb(i);
  }
  dfs();
  REP(i, 1, n) prt(ans[i]), pc(" \n"[i == n]);
}
posted @ 2022-06-22 23:50  ycx060617  阅读(148)  评论(0编辑  收藏  举报