1. 程式人生 > >UOJ#55 [WC2014]紫荊花之戀

UOJ#55 [WC2014]紫荊花之戀

line mov remove 點子 update treap ext clear inline

題目描述

強強和萌萌是一對好朋友。有一天他們在外面閑逛,突然看到前方有一棵紫荊樹。這已經是紫荊花飛舞的季節了,無數的花瓣以肉眼可見的速度從紫荊樹上長了出來。

仔細看看的話,這個大樹實際上是一個帶權樹。每個時刻它會長出一個新的葉子節點,每個節點上有一個可愛的小精靈,新長出的節點上也會同時出現一個新的小精靈。小精靈是很萌但是也很脆弱的生物,每個小精靈 \(i\) 都有一個感受能力值 \(r_i\),小精靈 \(i, j\) 成為朋友當且僅當在樹上 \(i\)\(j\) 的距離 \(\text{dist}(i, j) \leq r_i + r_j\),其中 \(\text{dist}(i, j)\)

表示在這個樹上從 \(i\)\(j\) 的唯一路徑上所有邊的邊權和。

強強和萌萌很好奇每次新長出一個葉子節點之後,這個樹上總共有幾對朋友。

我們假定這個樹一開始為空,節點按照加入的順序從 \(1\) 開始編號。由於強強非常好奇,你必須在他每次出現新結點後馬上給出總共的朋友對數,不能拖延哦。

輸入格式

第一行包含一個整數,表示測試點編號。

第二行包含一個正整數 \(n\),表示總共要加入的節點數。

我們令加入節點前的總共朋友對數是 \(\text{last_ans}\),在一開始時它的值為 \(0\)

接下來 \(n\) 行中第 \(i\) 行有三個非負整數 \(a_i, c_i, r_i\)

,表示結點 \(i\) 的父節點的編號為 \(a_i \oplus (\text{last_ans} \bmod 10^9)\)(其中 \(\oplus\) 表示異或,\(\bmod\)表示取余,數據保證這樣操作後得到的結果介於 \(1\)\(i - 1\) 之間),與父結點之間的邊權為 \(c_i\),節點 \(i\) 上小精靈的感受能力值為 \(r_i\)

註意 \(a_1 = c_1 = 0\),表示 \(1\) 號節點是根結點,對於 \(i > 1\),父節點的編號至少為 \(1\)

輸出格式

包含 \(n\) 行,每行輸出 \(1\) 個整數,表示加入第 \(i\) 個點之後,樹上有幾對朋友。

樣例一

input

    0
    5
    0 0 6
    1 2 4
    0 9 4
    0 5 5
    0 2 4

output

    0
    1
    2
    4
    7

樣例二

見樣例數據下載。

限制與約定

對於所有數據,滿足 \(1 \leq c_i \leq 10000\)\(a_i \leq 2 \times 10^9\)\(r_i \leq 10^9\)\(n \leq 100000\)

此題 hack 時忽略輸入數據中給定的測試點編號對測試點的限制。

祝大家一遍 AC,求不虐萌萌噠測評機!

時間限制\(12\texttt{s}\)

空間限制\(512\texttt{MB}\)

題解

首先,我們發現實際上每次只需要統計新加進的結點有多少合法解。那麽,我們將\(dist(i, j)\)寫成\(dist(i,p)+dist(j,p)\),其中\(p\)\(i\)\(j\)的LCA。

那麽,

\[\begin{aligned}dist(i,j)&\leq r_i + r_j\\Leftrightarrow dist(i,p)+dist(j,p)&\leq r_i+r_j\\Leftrightarrow r_i-dist(i,p)&\geq dist(j,p)-r_j\end{aligned}\]

所以,我們枚舉新加的結點的所有祖先\(p\),計算以\(p\)為LCA的滿足條件的點對\((i,j)\)有多少個即可。

計算時,先查詢\(p\)子樹在添加\(i\)之前有多少\(j\)滿足\(r_i-dist(i,p)\geq dist(j,p)-r_j\),再減去其中LCA不是\(p\)的(即,\(i\)\(j\)\(p\)的同一棵子樹裏,這樣我們就要查詢在這棵子樹裏有多少\(j\)滿足此條件)。

然而,要高效地維護這個信息,就需要在每個結點上維護一個Treap(記錄所有的\(dist(j,p)-r_j\)),但這樣當樹退化為鏈,單次加入時間復雜度增加為\(O(nlogn)\),空間復雜度增加為\(O(n^2)\)

於是,借鑒替罪羊樹的思想,我們在某個結點子樹內的點個數大於其父親子樹內點的個數的\(\alpha\in(0,1)\)倍的時候暴力重構其父親的子樹。既然要求重構之後盡量平衡,理所當然地選用點分治。

這樣,上面的分析中所有的“父親”和“祖先”都應理解為點分治樹上的祖先(LCA當然也是),但是答案仍是正確的,因為我們仍然不重不漏地枚舉了所有可能的點。

代碼實現上,還要寫一個倍增LCA以查詢\(dist\),因為點分治之後的祖先不一定是原樹上的祖先,不能直接用\(dep\)相減。

哦,還有,要寫垃圾回收,因為有重構treap,之前的內存必須要重復利用。

代碼

#include <algorithm>
#include <cstdio>
#include <cstdlib>
#include <stack>
#include <set>
typedef long long LL;
const int N = 100050;
int pre[N], to[N * 2], nxt[N * 2], c[N * 2], cnt = 0;
int r[N];
inline void add_edge(int x, int y, int v) {
  nxt[cnt] = pre[x];
  c[cnt] = v;
  to[pre[x] = cnt++] = y;
  nxt[cnt] = pre[y];
  c[cnt] = v;
  to[pre[y] = cnt++] = x;
}
namespace Tree{
  int dep[N], dep2[N], fa[N][17];
  void insert(int x, int c, int f) {
    add_edge(x, f, c);
    dep[x] = dep[fa[x][0] = f] + 1;
    dep2[x] = dep2[f] + c;
    for (int i = 1; i < 17; ++i)
      fa[x][i] = fa[fa[x][i - 1]][i - 1];
  }
  int LCA(int x, int y) {
    if (dep[x] > dep[y]) std::swap(x, y);
    for (int i = 16; ~i; --i)
      if (dep[fa[y][i]] >= dep[x])
        y = fa[y][i];
    for (int i = 16; ~i; --i)
      if (fa[x][i] != fa[y][i]) {
        x = fa[x][i];
        y = fa[y][i];
      }
    return x == y ? x : fa[x][0];
  }
  int dis(int x, int y) {
    int l = LCA(x, y);
    return dep2[x] + dep2[y] - 2 * dep2[l];
  }
};
struct Treap;
typedef Treap* PTreap;
struct Treap{
  static std::stack<PTreap> bin;
  PTreap lch, rch;
  int val, key, cnt, siz;
  void* operator new(size_t, int v) {
    Treap *res;
    res = bin.top();
    bin.pop();
    res->val = v; res->key = rand();
    res->cnt = 1; res->siz = 1;
    res->lch = res->rch = NULL;
    return res;
  }
  void operator delete(void *t) {
    bin.push((PTreap)t);
  }
  void update() {
    siz = cnt;
    if (lch != NULL) siz += lch->siz;
    if (rch != NULL) siz += rch->siz;
  }
  friend void Zig(PTreap &t) { //右旋
    PTreap l = t->lch;
    t->lch = l->rch;
    l->rch = t;
    t->update();
    l->update();
    t = l;
  }
  friend void Zag(PTreap &t) { //左旋
    Treap *r = t->rch;
    t->rch = r->lch;
    r->lch = t;
    t->update();
    r->update();
    t = r;
  }
  friend int query(PTreap o, int x) {
    if (o == NULL) return 0;
    if (o->val > x) return query(o->lch, x);
    else return query(o->rch, x) + (o->lch == NULL ? 0 : o->lch->siz) + o->cnt;
  }
  friend void insert(PTreap &o, int x) {
    if (o == NULL)
      o = new (x)Treap;
    else if (o->val == x)
      ++o->cnt;
    else if (o->val > x) {
      insert(o->lch, x);
      if (o->lch->key > o->key)
        Zig(o);
    } else {
      insert(o->rch, x);
      if (o->rch->key > o->key)
        Zag(o);
    }
    o->update();
  }
  friend void remove(PTreap &x) {
    if (x == NULL) return;
    remove(x->lch);
    remove(x->rch);
    delete x; x = NULL;
  }
};
std::stack<PTreap> Treap::bin;
namespace Dynamic_TreeDivision{
  PTreap tree[N], sonTree[N];
  int time, vise[N * 2];
  int fa[N], vis[N];
  std::set<int> son[N];
  void remove(int x) {
    vis[x] = time;
    for (std::set<int>::iterator i = son[x].begin(); i != son[x].end(); ++i) {
      remove(*i);
      remove(sonTree[*i]);
    }
    son[x].clear();
    remove(tree[x]);
  }
  int getCentre(int x, int f, int siz, int &ct) {
    int res = 1;
    bool ok = true;
    for (int i = pre[x]; ~i; i = nxt[i]) {
      if (vise[i] == time) continue;
      if (to[i] == f) continue;
      if (vis[to[i]] != time) continue;
      int ss = getCentre(to[i], x, siz, ct);
      if (ss > siz / 2) ok = false;
      res += ss;
    }
    if (siz - res > siz / 2) ok = false;
    if (ok) ct = x;
    return res;
  }
  void insertAll(int x, int f, int dep, PTreap &p) {
    insert(p, dep - r[x]);
    for (int i = pre[x]; ~i; i = nxt[i]) {
      if (vise[i] == time) continue;
      if (to[i] == f) continue;
      if (vis[to[i]] != time) continue;
      insertAll(to[i], x, dep + c[i], p);
    }
  }
  int divide(int x) {
    getCentre(x, 0, getCentre(x, 0, 1000000000, x), x);
    insertAll(x, 0, 0, tree[x]);
    for (int i = pre[x]; ~i; i = nxt[i]) {
      if (vise[i] == time) continue;
      if (vis[to[i]] != time) continue;
      vise[i] = vise[i ^ 1] = time;
      PTreap p = NULL;
      insertAll(to[i], 0, c[i], p);
      int s = divide(to[i]);
      fa[s] = x;
      son[x].insert(s);
      sonTree[s] = p;
    }
    return x;
  }
  void rebuild(int x) {
    ++time;
    remove(x);
    int ff = fa[x];
    PTreap p = sonTree[x];
    sonTree[x] = NULL;
    if (ff != 0) son[ff].erase(x);
    x = divide(x);
    fa[x] = ff;
    sonTree[x] = p;
    if (ff != 0) son[ff].insert(x);
  }
  LL insert(int x, int f) {
    LL ans = 0;
    son[f].insert(x);
    fa[x] = f;
    for (int i = x; i; i = fa[i]) {
      if (fa[i] != 0) {
        int d = Tree::dis(fa[i], x);
        ans += query(tree[fa[i]], r[x] - d);
        ans -= query(sonTree[i], r[x] - d);
        insert(sonTree[i], d - r[x]);
      }
      int d = Tree::dis(i, x);
      insert(tree[i], d - r[x]);
    }
    int rebuildx = 0;
    for (int i = x; fa[i]; i = fa[i])
      if (tree[i]->siz > tree[fa[i]]->siz * 0.88)
        rebuildx = fa[i];
    if (rebuildx) rebuild(rebuildx);
    return ans;
  }
};
Treap node[N * 100];
int main() {
  for (int i = 0; i < N * 100; ++i)
    Treap::bin.push(node + i);
  int n, a, cc, v;
  scanf("%*d%d", &n);
  LL lastans = 0;
  pre[0] = -1;
  for (int i = 1; i <= n; ++i) {
    scanf("%d%d%d", &a, &cc, &v);
    r[i] = v;
    a ^= lastans % 1000000000;
    pre[i] = -1;
    Tree::insert(i, cc, a);
    lastans += Dynamic_TreeDivision::insert(i, a);
    printf("%lld\n", lastans);
  }
  return 0;
}

UOJ#55 [WC2014]紫荊花之戀