AGC022F Checkers: A Dynamic Programming Approach on Multi-way Trees

We examine the problem of merging \(n\) initial unit vectors \(\mathbf{e}_1,\ldots,\mathbf{e}_n\) via operations that combine two vectors \(\mathbf{u},\mathbf{v}\) into either \(2\mathbf{u} - \mathbf{v}\) or \(2\mathbf{v} - \mathbf{u}\), depending on which one "wins". Each final vector’s \(i\)-th component is of the form \((-1)^{c_i}2^{d_i}\) for non‑negative integers \(c_i,d_i\). The exponent \(d_i\) equals the "depth" of element \(i\) in the merge tree, while \(c_i\) (mod 2) depends on the order of wins.

A convenient representation is a rooted multi‑way tree with \(n\) nodes, one per initial element. Each internal node corresponds to a winner that absorbs a set of losers. The children of a node are ordered to reflect the sequence of merges: the first child is the loser that was merged directly with the parent, then the next child is merged with the union of the parent and all previous children, etc. In this tree, if a node \(u\) has children \(v_1,\ldots,v_m\) in that order, then the parity contribution to \(c\) of a child \(v_j\) satisfies \(c_{v_j} \equiv c_u + (j-1) + s_u \pmod 2\), where \(s_u\) is the size of the subtree rooted at \(u\) (excluding \(u\)). The term \(j-1\) comes from the order, and \(s_u\) from the fact that the winning node \(u\) also defeats its own children.

Because the problem only cares about the multiset of the pairs \((c\bmod 2, d)\) (all permutation of the components are achievable), we count trees up to relabelling of the elements. Let \(cnt_{x}\) be the number of elements with a given \((c\bmod 2, d)\); the final count must be multiplied by \(n! / \prod cnt_x!\).

We design a DP that processes the tree level by level (from bottom to top). Let \(dp[i][j][k]\) be the sum over all multi‑way trees with \(i\) nodes, where the lowest layer (the leaves) contains exactly \(j\) nodes whose parent has an odd subtree size (i.e., the "odd‑size" nodes in the next‑to‑last layer), and all those \(j\) nodes share the same parity \(k\) for \(c\bmod2\). The last layer’s nodes themselves have \(s=0\), so their parity is determined solely by \(c_{\text{parent}}\) and the order index \(\pi\).

To build a layer, we choose \(x\) nodes with \(c'\equiv0\) and \(y\) nodes with \(c'\equiv1\) from the potential "even‑size" parents, where \(c' = c - s\). The final number of nodes in the new layer is \(x+y+j\). The parity distribution among those new nodes is computed from \(x,y,j,k\). The transition becomes:


\[
dp[i][j][k] = \sum_{\substack{x,y\ge0\\ x+y\leq i-1-j\\ (j>0\text{ or }x>0\text{ or }y>0)}} \frac{1}{(x + (1-k)j)!\,(y + k j)!} \cdot dp[i-j-x-y][\,|x'-y'|\,][\,[x'>y']\,]
\]
where \(x' = x + (k=1)j\), \(y' = y + (k=0)j\).

This naive quadruple loop runs in \(O(n^4)\), which is acceptable for the original AGC but too slow for some contests. We can optimize to \(O(n^3)\) by pre‑computing inner sums. For fixed \(i,j,k\) and a given \(x\) (when \(k=0\)) or \(y\) (when \(k=1\)), the inner sum over the remaining variable depends only on a small number of parameters, allowing us to cache results. The implementation below uses a 4‑D cache indexed by \(i-j-x\), \(x + (\text{signed term}) + n\), \(k\), and a boolean indicating whether \(j\) or \(x\) is non‑zero.

const int N = 510;
int n, P;
int inv[N], fac[N], ifac[N];
int dp[N][N][2];
ull cache[N][3 * N][2][2];
bool vis[N][3 * N][2];

void solve() {
    n = read(); P = read();
    inv[1] = 1;
    for (int i = 2; i <= n; ++i)
        inv[i] = (ll)inv[P % i] * (P - P / i) % P;
    fac[0] = ifac[0] = 1;
    for (int i = 1; i <= n; ++i) {
        fac[i] = (ll)fac[i - 1] * i % P;
        ifac[i] = (ll)ifac[i - 1] * inv[i] % P;
    }
    dp[1][0][0] = dp[1][0][1] = dp[1][1][1] = 1;
    memset(cache, -1, sizeof(cache));

    for (int i = 2; i <= n; ++i) {
        for (int j = 0; j <= i - 1; ++j) {
            for (int k = 0; k <= 1; ++k) {
                // k = 0: outer loop over x, inner over y
                if (k == 0) {
                    for (int x = 0; x <= i - 1 - j; ++x) {
                        int idx = i - j - x;
                        int off = x + (k ? j : -j) + n;
                        int flag = (j || x);
                        ull &F = cache[idx][off][k][flag];
                        if (!~F) {
                            F = 0;
                            int cnt = 0;
                            for (int y = 0; y <= i - 1 - j - x; ++y) {
                                if (!j && !x && !y) continue;
                                int x0 = x + (k == 1) * j;
                                int y0 = y + (k == 0) * j;
                                int y00 = y + (k == 1) * j;
                                int nxt = (x0 < y0) ? dp[i - j - x - y][y0 - x0][0] : dp[i - j - x - y][x0 - y0][1];
                                F += (ll)ifac[y00] * nxt;
                                if (++cnt == 18) { F %= P; cnt = 0; }
                            }
                            F %= P;
                        }
                        int x00 = x + (k == 0) * j;
                        dp[i][j][k] = (dp[i][j][k] + (ll)ifac[x00] * F) % P;
                    }
                } else {
                    // k = 1: swap roles of x and y
                    for (int y = 0; y <= i - 1 - j; ++y) {
                        int idx = i - j - y;
                        int off = y + (!k ? j : -j) + n;
                        int flag = (j || y);
                        ull &F = cache[idx][off][k][flag];
                        if (!~F) {
                            F = 0;
                            int cnt = 0;
                            for (int x = 0; x <= i - 1 - j - y; ++x) {
                                if (!j && !x && !y) continue;
                                int x0 = x + (k == 1) * j;
                                int y0 = y + (k == 0) * j;
                                int x00 = x + (k == 0) * j;
                                int nxt = (x0 < y0) ? dp[i - j - x - y][y0 - x0][0] : dp[i - j - x - y][x0 - y0][1];
                                F += (ll)ifac[x00] * nxt;
                                if (++cnt == 18) { F %= P; cnt = 0; }
                            }
                            F %= P;
                        }
                        int y00 = y + (k == 1) * j;
                        dp[i][j][k] = (dp[i][j][k] + (ll)ifac[y00] * F) % P;
                    }
                }
            }
        }
    }
    int ans = (ll)fac[n] * dp[n][0][0] % P;
    printf("%d\n", ans);
}

The final answer is \(n! \cdot dp[n][0][0] \bmod P\). The optimization reduces the complexity to \(O(n^3)\) with careful grouping of inner loops.

Tags: AtCoder AGC022F Dynamic Programming combinatorics trees

Posted on Tue, 15 Sep 2026 16:36:04 +0000 by cneale