P10013 [集訓隊互測 2023] Tree Topological Order Counting

下蛋爷發表於2024-08-29

Description

給定一顆 \(n\) 個點的有根樹,\(1\) 是根,記 \(u\) 的父親是 \(fa_u\)。另給出一長度為 \(n\) 的權值序列 \(b\)

稱一個長度為 \(n\) 的排列 \(a\) 為這顆樹的合法拓撲序,當且僅當 \(\forall 2 \le u \le n,a_u > a_{fa_u}\)

對每個點 \(u\),定義 \(f(u)\) 為,在所有這顆樹的合法拓撲序中,\(b_{a_u}\) 之和。

現在對 \(1 \le u \le n\),求 \(f(u) \bmod 10^9+7\)

\(2 \le n \le 5000\)\(1 \le fa_i < i\)\(0 \le b_i < 10^9+7\)

Solution

先考慮對於每個 \(x\) 怎麼求出答案。

假設當前處理到了節點 \(u\)(要求 \(u\)\(x\) 的祖先),\(v\)\(u\) 的兒子中子樹包含 \(x\) 的兒子。

\(f_u\) 表示 \(u\) 的子樹的合法拓撲序數量,\(g_{u,i}\) 表示 \(u\) 的子樹裡已經欽定恰好有 \(i\) 個點 dfs 序小於 \(x\) 的方案數,\(h_u\) 表示 \(u\) 的子樹去掉包含 \(x\) 的子樹剩下的點的合法拓撲序數。可以得到轉移:

\[\begin{aligned} f_u&=(sz_u-1)!\prod_{w\in \text{son}_u}{f_w/(sz_w!)}\\ h_u&=\prod_{w\in \text{son}_u,w\neq v}{f_w/(sz_w!)}\\ g_{u,i+j+1}&=\sum_{0\leq i\leq sz_v-1,0\leq j\leq sz_u-sz_v-1}{\binom{i+j}{j}\binom{sz_u-i-j-2}{sz_v-i-1}h_ug_{v,i}} \end{aligned} \]

最終的答案即為 \(\sum_{i=1}^{n}{g_{1,i-1}b_i}\)

時間複雜度:\(O(n^3)\)


考慮怎麼最佳化。

容易發現這題主要慢在每次要處理從一個點到根的路徑上的 dp,而不是從根到某個點的路徑,這樣會導致每個點之間的資訊沒有任何交集。

注意到這題轉移的終點是一定的,就是根,而起點不一樣。所以可以類似這題的思路把轉移倒過來做。

具體的,將 \(g_{u,i}\) 的狀態改為當前狀態為 \((u,i)\),到最終狀態對答案的貢獻。轉移改為:

\[g_{v,i}=\sum_{0\leq i\leq sz_v-1,0\leq j\leq sz_u-sz_v-1}{\binom{i+j}{j}\binom{sz_u-i-j-2}{sz_v-i-1}h_ug_{u,i+j+1}} \]

最終 \(x\) 的答案就是 \(f_xg_{x,0}\)

時間複雜度:\(O(n^2)\)

Code

#include <bits/stdc++.h>

#define int int64_t

const int kMaxN = 5e3 + 5, kMod = 1e9 + 7;

int n;
int a[kMaxN], C[kMaxN][kMaxN], sz[kMaxN], f[kMaxN], g[kMaxN][kMaxN];
std::vector<int> G[kMaxN];

constexpr int qpow(int bs, int64_t idx = kMod - 2) {
  int ret = 1;
  for (; idx; idx >>= 1, bs = (int64_t)bs * bs % kMod)
    if (idx & 1)
      ret = (int64_t)ret * bs % kMod;
  return ret;
}

inline int add(int x, int y) { return (x + y >= kMod ? x + y - kMod : x + y); }
inline int sub(int x, int y) { return (x >= y ? x - y : x - y + kMod); }
inline void inc(int &x, int y) { (x += y) >= kMod ? x -= kMod : x; }
inline void dec(int &x, int y) { (x -= y) < 0 ? x += kMod : x; }

void prework() {
  C[0][0] = 1;
  for (int i = 1; i <= n; ++i) {
    C[i][0] = 1;
    for (int j = 1; j <= i; ++j)
      C[i][j] = add(C[i - 1][j], C[i - 1][j - 1]);
  }
}

void dfs1(int u) {
  f[u] = 1, sz[u] = 0;
  for (auto v : G[u]) {
    dfs1(v);
    f[u] = 1ll * f[u] * f[v] % kMod * C[sz[u] += sz[v]][sz[v]] % kMod;
  }
  ++sz[u];
}

void dfs2(int u) {
  for (auto v : G[u]) {
    int coef = 1, now = 0;
    for (auto w : G[u]) {
      if (w != v) coef = 1ll * coef * f[w] % kMod * C[now += sz[w]][sz[w]] % kMod;
    }
    for (int i = 0; i <= sz[v] - 1; ++i)
      for (int j = 0; j <= sz[u] - sz[v] - 1; ++j)
        inc(g[v][i], 1ll * coef * C[i + j][j] % kMod * C[sz[u] - i - j - 2][sz[v] - i - 1] % kMod * g[u][i + j + 1] % kMod);
    dfs2(v);
  }
}

void dickdreamer() {
  std::cin >> n;
  for (int i = 2; i <= n; ++i) {
    int p;
    std::cin >> p;
    G[p].emplace_back(i);
  }
  for (int i = 1; i <= n; ++i) std::cin >> a[i];
  prework();
  for (int i = 1; i <= n; ++i) g[1][i - 1] = a[i];
  dfs1(1), dfs2(1);
  for (int i = 1; i <= n; ++i) std::cout << 1ll * f[i] * g[i][0] % kMod << ' ';
}

int32_t main() {
#ifdef ORZXKR
  freopen("in.txt", "r", stdin);
  freopen("out.txt", "w", stdout);
#endif
  std::ios::sync_with_stdio(0), std::cin.tie(0), std::cout.tie(0);
  int T = 1;
  // std::cin >> T;
  while (T--) dickdreamer();
  // std::cerr << 1.0 * clock() / CLOCKS_PER_SEC << "s\n";
  return 0;
}

相關文章