アルゴリズム:最小 - 最大と除外、木のバックパック、NTT。
問題の意味の説明
あります\(N \)ツリーポイントは。すべての開始点は白色で、各操作がランダムに選択する(\ \ FRAC {N \倍 (N + 1)} {2} \) 経路、黒へのパス上のすべての点の。ポイントのすべての期待数は操作が黒く塗りつぶされている求めています。
\(N- \ル50 \) 。複数のデータセット。(998、244、353 \)\剰余。
問題の解決策
日常的には、我々は最小-最大と除外を使用しています。
\ [開始\ {整列} E (\ MAX(U))&= \和_ {\ PHI \ NEQ S \サブセットU}(-1)^ {\ lvert S \ rvert - 1} E(\分(S) )\\&= \和_ { \ PHI \ NEQ S \サブセットU}(-1)^ {\ lvert S \ rvert - 1} \ FRAC {1} {P( \ S \点の内部を通るルート)} \ \&= \和_ {\ PHI \ NEQ S \サブセットU}(-1)^ {\ lvert S \ rvert - 1} \ FRAC {1} {1 - P( 点を通過せずにパス\ S \で)} \端{整列} \]
我々は木を引き出す場合、および\(S \)タグ通信の複数のブロックに、元のツリーのポイントを見つけることができ、各ブロックの内部との通信は、任意に選択してもよい、でパス、パスが通過しないように(S \)\そこポイント。ブロックを横断通信経路と、それが通過しなければならない(S \)\そこポイント。
分割されると呼ぶ\(X \)相互接続ブロック\(T_L、T_2、\ cdots、T_x \)通過することなく、(S \)\パス番号
\ [\ sum_ {I = 1 } ^ {X} \ FRAC {\ lvert T_I \ rvert
\回\左(\ lvert T_I \ rvert + 1 \右)} {2} \] 次に、確率
\ [\ FRAC {\ sum_ { iは1} ^ {X} \ lvertを= T_I \ rvert \回\左(
\ lvert T_I \ rvert + 1 \右)} {N \倍(N + 1)} \] 与えるために、元の式を簡略化するために続行
\ [\ E {整列}開始 (MAX(U)\)&= \和_ {\ PHI \ NEQ S \サブセットU}(-1)^ {\ lvert S \ rvert - 1} \ FRAC {1} {1 - \ dfrac {\ sum_ {I = 1} ^ {X} \ lvert T_I \ rvert \回\左(\ lvert T_I \ rvert + 1 \右)} {N \倍(N + 1)}} \\& = \和_ {\ PHI \ NEQ S \サブセットU}(-1)^ {\ lvert S \ rvert - 1} \ FRAC {N \倍(N + 1)} {N \倍(N + 1) - \ sum_ {I = 1} ^ { X} \ lvert T_I \ rvert \回\左(\ lvert T_I \ rvert + 1 \右)} \\&= \ FRAC {N \倍(N + 1)} {2} \和_ {\ PHI \ NEQ S \サブセットU}(-1)^ {\ lvert S \ rvert - 1} \ FRAC {1} {\ dfrac {N \倍(N + 1)} {2} - \ sum_ {i = 1} ^ {X
} \ dfrac {\ lvert T_I \ rvert \回\左(\ lvert T_I \ rvert + 1 \右)} {2}} \端{整列} \] 最終場合、ある部門の答えを持って来るために拠出制度のみ\(( - 1)^ { | S | - 1} \) と\(\ sum_ {iは1 = ^ X \ dfracを} {\ lvert T_I \ rvert \回\左( \ lvert T_I \ rvert + 1 \右)} {2} \)二つ。この情報を得るために、我々は、唯一の実施形態では、偶数、奇数の選択されたポイント数に興味を持っている、プログラムの数(\ \ sum_ {i = 1 } ^ X \ dfrac {\ lvert T_I \ rvert \回\左( \ lvert T_I \ rvert + 1 \右)} {2} \) 私の列挙定数の\(K \) 。
この問題を解決するために、動的プログラミングのデザインを考えてみましょう。私たちは、最初の選択(1 \)\ツリー全体のルートは、ツリーが根付い木なるようドット。
注\(F [i]の[奇数 ] [j] [k]は\) のみと考え表す\(I \)を含む、点の奇数/偶数から選択ルートとするサブツリー、\(I \)通信しブロックサイズ\(J \)、\ (\ sum_ {私は= 1} ^ X \ dfrac {\ lvert T_I \ rvert \時間\は(\ lvert T_I \ rvert +左1 \右)} {2} \)(フリー\(I \)通信ブロックの値)\(K \)番号スキーム。前記\(J = 0 \)を表し\(I \)ドットブロックが通信していない(すなわち、選択された点に集束)。
さらに、我々は、アレイを示す\(G [i]の[奇数 [K] \) で表される(I \)\サブツリーのルートであっても、ドット奇数/から選択される\(\ sum_ {私は= 1} X ^ \ dfrac {\ lvert T_I \ rvert \時間\(\ lvert T_I \ rvert +。1 \右)左} {2} \) (を含む\(Iは\)通信ブロックに位置している)\(K \)プログラムの数。明確
\ [G [i]は[奇数 ] [K「] = \ sum_ {J = 0} ^ N \ sum_ {k = 0} ^ {\ FRAC {N \倍(N + 1)} {2}} \左[\ FRAC {J \回(J + 1)} {2} + K == K「\右] F [I] [J] [奇数] [K] \]
ツリーに似転送バックパック。合わせ\(I \)と息子\(V \) 、式の転移:
\ [F [I] [ODD]は[J] [K] = \ \ {\ \整列{開始}&sum_ {ODDを残し'\ \ {1、0 \}} \ sum_ {K' = 0} ^ {K} F [i]は[奇数] [J] [K '] [\ CDOT G [V]、奇数\ oplus奇数'] [K - K '] \クワッド &J = 0 \\&\ sum_ {奇数' \で\ {0,1 \}} \ sum_ {J '= 1} ^ {J} \ sum_ {K' = 0} ^ {K} F [i]は [奇数] [j] [k]の\ CDOT F [v] [oplus \奇数奇数'] [J - J'] [K - K「] \クワッド&J \ NEQ 0 \ 端{整列} \右。\
] 転写複雑暴力\(\ mathcal {O}( N ^ 7)\) と許容できません。
私たちは、ことに注意してください(J \)\超えていない次元のサイズ(私は\)\サブツリーのサイズのは、これは木のバックパックの古典的な形式です。従って少ない複雑\(N- \) 、となる\(\ mathcal {O}(N ^ 6)\) 。
それが発見された、観察し続ける\(K \)転送が一次元畳み込みの形態である、NTTは、最適化のために使用することができます。言い換えると、に加えて\(F [I] \)の\(G [i]が\)ここで算出された寄与、我々は状態の最後の次元として取る場合\(\ FRAC {N \倍 (N + 1)} {2} \)の多項式は、全て本の多項式の計算、多項式乗算及び加算とみなすことができます。私たちは法の先頭にポイント値を表すことができるように表し\(F \)と\(グラム\)だけに値\(F \)に\(グラム\)カウントの貢献は、我々はINTTを実施、値が与える、ポイント係数表現に還元バックを表す(G \)\後、再びポイント値表現に低減することができます。
元のように\(\ mathcal {O}( N ^ 2)\) 転送がに最適化されている\(\ mathcal {O}(\)N-ログ\) 。この全体的な複雑さの後になる\(^ \ mathcal {O}(N-4 \ N-ログ)\) 。
場合、各プログラムの実行Iの試験データ\(0.5 \テキスト{S} \)質問するデータセットの数\(T = 15 \)カード後、私は一定でした。現時点では前者のみだった\(9 \)ポイントは、最後の\(3 \)ポイントは、まだ通常のカードに苦労しています。
#include <algorithm>
#include <cstdio>
#include <cstring>
const int MaxN = 50 + 5;
const int MaxV = 4096 + 5;
const int Mod = 998244353, Prt = 3;
struct Graph {
int cnte;
int Head[MaxN], To[MaxN * 2], Next[MaxN * 2];
inline void clear() {
cnte = 0;
memset(Head, 0, sizeof Head);
memset(To, 0, sizeof To);
memset(Next, 0, sizeof Next);
}
inline void addEdge(int from, int to) {
cnte++; To[cnte] = to;
Next[cnte] = Head[from]; Head[from] = cnte;
}
};
int Te, N;
int Fa[MaxN], Siz[MaxN];
int F[MaxN][2][MaxN][MaxV], G[MaxN][2][MaxV];
int Rev[MaxV][MaxV], W[2][MaxV], Inv[MaxV];
Graph Gr;
inline int add(int x, int y) { return (x += y) >= Mod ? x - Mod : x; }
inline int sub(int x, int y) { return (x -= y) < 0 ? x + Mod : x; }
inline int mul(int x, int y) { return 1LL * x * y % Mod; }
inline int pw(int x, int y) { int z = 1; for (; y; y >>= 1, x = mul(x, x)) if (y & 1) z = mul(z, x); return z; }
inline int inv(int x) { return pw(x, Mod - 2); }
inline int sep(int x, int y) { return mul(x, inv(y)); }
inline void inc(int &x, int y = 1) { x = add(x, y); }
inline void dec(int &x, int y = 1) { x = sub(x, y); }
void init() {
scanf("%d", &N);
for (int i = 1; i < N; ++i) {
int u, v;
scanf("%d %d", &u, &v);
Gr.addEdge(u, v);
Gr.addEdge(v, u);
}
}
void dfs1(int u) {
Siz[u] = 1;
for (int i = Gr.Head[u]; i; i = Gr.Next[i]) {
int v = Gr.To[i];
if (v == Fa[u]) continue;
Fa[v] = u;
dfs1(v);
Siz[u] += Siz[v];
}
}
inline void ntt(int *a, int n, int f) {
for (int i = 1; i < n; ++i)
if (i < Rev[n][i]) std::swap(a[i], a[Rev[n][i]]);
for (int i = 1; i < n; i <<= 1) {
int w = W[f][i];
for (int j = 0; j < n; j += (i << 1)) {
int x = 1;
for (int k = 0; k < i; ++k, x = mul(x, w)) {
int lson = a[j + k], rson = a[i + j + k];
a[j + k] = add(lson, mul(rson, x));
a[i + j + k] = sub(lson, mul(rson, x));
}
}
}
if (f == 1)
for (int i = 0; i < n; ++i) a[i] = mul(a[i], Inv[n]);
}
inline int getPow2(int n) {
int v = 1;
while (v < n) v <<= 1;
return v;
}
inline void calcG(int u) {
for (int odd = 0; odd <= 1; ++odd) {
for (int j = 0; j <= N; ++j)
for (int k = 0; k <= N * (N + 1) / 2; ++k) {
int newK = j * (j + 1) / 2 + k;
if (newK > N * (N + 1) / 2) break;
inc(G[u][odd][newK], F[u][odd][j][k]);
}
}
}
void dfs2(int u) {
F[u][1][0][0] = F[u][0][1][0] = 1;
int sz = 1;
for (int i = Gr.Head[u]; i; i = Gr.Next[i]) {
int v = Gr.To[i];
if (v == Fa[u]) continue;
dfs2(v);
sz += Siz[v];
static int f[2][MaxN][MaxV];
int len = getPow2(Siz[u] * (Siz[u] + 1));
for (int odd = 0; odd <= 1; ++odd)
for (int j = 0; j <= sz; ++j)
for (int k = 0; k < len; ++k)
f[odd][j][k] = 0;
for (int odd = 0; odd <= 1; ++odd) {
ntt(G[v][odd], len, 0);
for (int j = 0; j <= sz - Siz[v]; ++j) ntt(F[u][odd][j], len, 0);
for (int j = 0; j <= Siz[v]; ++j) ntt(F[v][odd][j], len, 0);
}
for (int odd = 0; odd <= 1; ++odd) {
for (int j = 0; j <= sz; ++j) {
for (int odd2 = 0; odd2 <= 1; ++odd2) {
if (j == 0) {
for (int k = 0; k < len; ++k)
inc(f[odd][j][k], mul(F[u][odd ^ odd2][j][k], G[v][odd2][k]));
} else {
for (int j2 = std::max(0, j - sz + Siz[v]); j2 <= Siz[v] && j2 < j; ++j2) {
for (int k = 0; k < len; ++k)
inc(f[odd][j][k], mul(F[u][odd ^ odd2][j - j2][k], F[v][odd2][j2][k]));
}
}
}
}
}
for (int odd = 0; odd <= 1; ++odd) {
ntt(G[v][odd], len, 1);
for (int j = 0; j <= sz - Siz[v]; ++j) ntt(F[u][odd][j], len, 1);
for (int j = 0; j <= Siz[v]; ++j) ntt(F[v][odd][j], len, 1);
for (int j = 0; j <= sz; ++j) ntt(f[odd][j], len, 1);
}
for (int odd = 0; odd <= 1; ++odd)
for (int j = 0; j <= sz; ++j)
for (int k = 0; k <= sz * (sz + 1) / 2; ++k)
F[u][odd][j][k] = f[odd][j][k];
}
calcG(u);
}
void solve() {
dfs1(1);
dfs2(1);
int ans = 0;
for (int odd = 0; odd <= 1; ++odd)
for (int k = 0; k < N * (N + 1) / 2; ++k) {
if (odd == 1) inc(ans, mul(G[1][odd][k], inv(N * (N + 1) / 2 - k)));
else dec(ans, mul(G[1][odd][k], inv(N * (N + 1) / 2 - k)));
}
ans = mul(ans, N * (N + 1) / 2);
printf("%d\n", ans);
}
void clear() {
memset(Fa, 0, sizeof Fa);
memset(Siz, 0, sizeof Siz);
memset(F, 0, sizeof F);
memset(G, 0, sizeof G);
Gr.clear();
}
int main() {
for (int i = 1; i <= 4096; i <<= 1) {
Rev[i][0] = 0;
for (int j = 1; j < i; ++j) {
Rev[i][j] = (Rev[i][j >> 1]) >> 1;
if (j & 1) Rev[i][j] |= (i >> 1);
}
Inv[i] = inv(i);
}
for (int f = 0; f <= 1; ++f)
for (int i = 1; i < 4096; i <<= 1)
W[f][i] = pw(pw(Prt, f == 0 ? 1 : Mod - 2), (Mod - 1) / (i << 1));
scanf("%d", &Te);
for (int t = 1; t <= Te; ++t) {
init();
printf("Case #%d: ", t);
solve();
clear();
}
return 0;
}