Search for a command to run...
Progressive hints first, then the full explanation and implementation when you're ready to cash out.
Review status
AI-generated and still unreviewed. Double-check the details before internalizing them.
Hints
Open only as much as you need to keep the solve alive.
An operation cuts exactly an edge whose child-side component has even size. So stop thinking about colors as decorations; white vertices are just even subtree cuts wearing a silly hat.
Root the initial tree and compute every subtree size. Edges whose child subtree is odd are special: they can never be cut. Moving even chunks around can only add or remove even amounts on either side, so their child-side parity stays odd.
Remove every edge with even child-subtree size. The remaining connected components are fixed forever. Operations only rearrange edges between these components.
Inside a fixed component, all non-root vertices stay black. White vertices correspond exactly to the entry vertex of every non-root component in the contracted tree, plus the root itself when is even.
A final tree is conquered iff the contracted component tree is a path starting at the root component. If component sizes are , count all ordered paths from the root component with edge weight .
Let’s strip the problem down to what actually matters: parity.
A non-root vertex is white iff the edge from its parent to it cuts off an even-sized component. So the operation is:
The recoloring step is not magic; it just recomputes these parities.
Fixed Edges
Root the original tree at . For every edge where is the parent of , look at .
If is odd, that edge is never removable.
Why? Suppose that edge is still present later. Other operations may move whole even-sized chunks across the area, but they only add/subtract even sizes from either side of this edge. Odd plus/minus even is still odd. So the side below that edge always has odd size whenever it is oriented away from the root. It never becomes a legal white cut.
So these odd-subtree edges are rigid. They are the bones of the tree.
Now delete every edge with even child-subtree size from the original tree. The remaining graph is a forest. Call its connected components blocks.
These blocks are fixed forever:
So the whole problem becomes a problem about rearranging a tree of blocks. The original labels inside each block still matter only because they give choices for endpoints.
Block Parities
Let the block containing vertex be the root block.
Every non-root block has even size. The root block has parity equal to :
Every edge between blocks cuts off a union of non-root blocks, hence an even number of vertices. Therefore every block-tree edge is always legally cuttable. Nice: after compression, operations can rearrange the block tree basically however they want.
More precisely, all expanded spanning trees on the blocks are reachable. For an edge between block and block , there are possible real edges, where is the number of original vertices in block .
When Is A Final Tree Conquered?
Inside a block, all internal child-subtree sizes are odd, so those vertices are black.
White vertices appear only at block entrances:
Thus the conquered condition says:
all non-root blocks must lie on one root-to-some-block chain.
In other words, the contracted block tree must be a path whose one endpoint is the root block.
That is the whole damn problem.
Counting Weighted Rooted Paths
Suppose there are blocks. If , no block edges exist, so the answer is .
Otherwise, choose an ordering of all non-root blocks:
This represents the block path starting at the root block .
For each adjacent pair , we choose one actual vertex in block and one actual vertex in block , giving choices.
So this ordering contributes
That product equals
So every middle non-root block contributes squared size, while the last block contributes only once.
Sum over the choice of the last block. The remaining blocks can be ordered arbitrarily, giving orders.
Therefore:
computed modulo .
The modular inverses are valid because every block size is at most , way below the modulus.
Algorithm
For each test case:
Complexity is per test case, basically linear.
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
const ll MOD = 998244353;
void setIO() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
}
ll modpow(ll a, ll e) {
ll r = 1;
while (e) {
if (e & 1) r = r * a % MOD;
a = a * a % MOD;
e >>= 1;
}
return r;
}
struct DSU {
vector<int> p, sz;
DSU(int n = 0) { init(n); }
void init(int n) {
p.resize(n + 1);
sz.assign(n + 1, 1);
iota(p.begin(), p.end(), 0);
}
int find(int x) {
while (x != p[x]) {
p[x] = p[p[x]];
x = p[x];
}
return x;
}
void unite(int a, int b) {
a = find(a), b = find(b);
if (a == b) return;
if (sz[a] < sz[b]) swap(a, b);
p[b] = a;
sz[a] += sz[b];
}
};
int main() {
setIO();
int T;
cin >> T;
const int MAXN = 200000;
vector<ll> fact(MAXN + 1, 1);
for (int i = 1; i <= MAXN; i++) fact[i] = fact[i - 1] * i % MOD;
while (T--) {
int n;
cin >> n;
vector<vector<int>> g(n + 1);
for (int i = 0; i < n - 1; i++) {
int u, v;
cin >> u >> v;
g[u].push_back(v);
g[v].push_back(u);
}
vector<int> parent(n + 1, 0), order;
order.reserve(n);
order.push_back(1);
parent[1] = -1;
for (int i = 0; i < n; i++) {
int v = order[i];
for (int to : g[v]) {
if (to == parent[v]) continue;
parent[to] = v;
order.push_back(to);
}
}
vector<int> sub(n + 1, 1);
for (int i = n - 1; i > 0; i--) {
int v = order[i];
sub[parent[v]] += sub[v];
}
DSU dsu(n);
for (int v = 2; v <= n; v++) {
if (sub[v] & 1) dsu.unite(v, parent[v]);
}
unordered_map<int, int> id;
id.reserve(n * 2);
vector<int> blockSize;
for (int v = 1; v <= n; v++) {
int r = dsu.find(v);
auto it = id.find(r);
if (it == id.end()) {
int nxt = (int)blockSize.size();
id[r] = nxt;
blockSize.push_back(0);
it = id.find(r);
}
blockSize[it->second]++;
}
int m = (int)blockSize.size();
if (m == 1) {
cout << 1 << '\n';
continue;
}
int rootBlock = id[dsu.find(1)];
ll rootSize = blockSize[rootBlock];
ll prodSq = 1;
ll sumInv = 0;
for (int i = 0; i < m; i++) {
if (i == rootBlock) continue;
ll s = blockSize[i];
prodSq = prodSq * s % MOD * s % MOD;
sumInv = (sumInv + modpow(s, MOD - 2)) % MOD;
}
ll ans = rootSize % MOD;
ans = ans * fact[m - 2] % MOD;
ans = ans * prodSq % MOD;
ans = ans * sumInv % MOD;
cout << ans << '\n';
}
return 0;
}#include <bits/stdc++.h>
using namespace std;
using ll = long long;
const ll MOD = 998244353;
void setIO() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
}
ll modpow(ll a, ll e) {
ll r = 1;
while (e) {
if (e & 1) r = r * a % MOD;
a = a * a % MOD;
e >>= 1;
}
return r;
}
struct DSU {
vector<int> p, sz;
DSU(int n = 0) { init(n); }
void init(int n) {
p.resize(n + 1);
sz.assign(n + 1, 1);
iota(p.begin(), p.end(), 0);
}
int find(int x) {
while (x != p[x]) {
p[x] = p[p[x]];
x = p[x];
}
return x;
}
void unite(int a, int b) {
a = find(a), b = find(b);
if (a == b) return;
if (sz[a] < sz[b]) swap(a, b);
p[b] = a;
sz[a] += sz[b];
}
};
int main() {
setIO();
int T;
cin >> T;
const int MAXN = 200000;
vector<ll> fact(MAXN + 1, 1);
for (int i = 1; i <= MAXN; i++) fact[i] = fact[i - 1] * i % MOD;
while (T--) {
int n;
cin >> n;
vector<vector<int>> g(n + 1);
for (int i = 0; i < n - 1; i++) {
int u, v;
cin >> u >> v;
g[u].push_back(v);
g[v].push_back(u);
}
vector<int> parent(n + 1, 0), order;
order.reserve(n);
order.push_back(1);
parent[1] = -1;
for (int i = 0; i < n; i++) {
int v = order[i];
for (int to : g[v]) {
if (to == parent[v]) continue;
parent[to] = v;
order.push_back(to);
}
}
vector<int> sub(n + 1, 1);
for (int i = n - 1; i > 0; i--) {
int v = order[i];
sub[parent[v]] += sub[v];
}
DSU dsu(n);
for (int v = 2; v <= n; v++) {
if (sub[v] & 1) dsu.unite(v, parent[v]);
}
unordered_map<int, int> id;
id.reserve(n * 2);
vector<int> blockSize;
for (int v = 1; v <= n; v++) {
int r = dsu.find(v);
auto it = id.find(r);
if (it == id.end()) {
int nxt = (int)blockSize.size();
id[r] = nxt;
blockSize.push_back(0);
it = id.find(r);
}
blockSize[it->second]++;
}
int m = (int)blockSize.size();
if (m == 1) {
cout << 1 << '\n';
continue;
}
int rootBlock = id[dsu.find(1)];
ll rootSize = blockSize[rootBlock];
ll prodSq = 1;
ll sumInv = 0;
for (int i = 0; i < m; i++) {
if (i == rootBlock) continue;
ll s = blockSize[i];
prodSq = prodSq * s % MOD * s % MOD;
sumInv = (sumInv + modpow(s, MOD - 2)) % MOD;
}
ll ans = rootSize % MOD;
ans = ans * fact[m - 2] % MOD;
ans = ans * prodSq % MOD;
ans = ans * sumInv % MOD;
cout << ans << '\n';
}
return 0;
}