Submission #810905

#TimeUsernameProblemLanguageResultExecution timeMemory
810905jophyyjhDigital Circuit (IOI22_circuit)C++17
100 / 100
1036 ms32700 KiB
/**
 * I nearly fully-solved this problem in IOI 2022, though I ran out of time. Initially, I
 * was only hoping to score more partials, but it turned out to be a full solution.
 * 
 * For [S1-3], DFS + dp suffice: for each node u in the tree, we compute dp1[u], the num of
 * different assignments of parameters to threshold gates in the subtree of u that will
 * result in u being 1. Similarly, we can define dp0[u].
 * Let v_1, v_2, ..., v_k be all the children of u. Mathematically,
 *      dp1[u] = (dp1[v_1] * dp1[v_2] * ... * dp1[v_k]) * k      <-- k options for u
 *                      -->   + (dp1[v_1] * ... * dp1[v_{k-1}] * dp0[v_k])
 *  (k-1) options for u -->   +  ...
 *                      -->   +  dp0[v_1] * dp1[v_2] * ... * dp1[v_k]) * (k-1)
 *              + ...,
 * and dp0[u] can be found from dp1[u]. 
 * 
 * The huge sum can be computed quite efficiently, again with dp. For each node u, let
 * partial[i] (0 <= i <= k) be
 *    sum{ (dp0[v_1] or dp1[v_1]) * ... * (dp0[v_k] or dp1[v_k]) : exactly i dp1[]'s }.
 * We can find each partial[i] iteratively:
 *         partial[i] <-- partial[i] * dp0[some_v] + partial[i-1] * dp1[some_v].
 * Therefore, dp1[u] = partial[1] * 1 + ... + partial[k] * k. So, O((N+M)^2 * Q). [S4] can
 * be solved too, because we only have to update O(log(N)) dp1[]'s and dp0[]'s in each
 * query.
 * 
 * I thought there must be ways to optimize this dp. Because the source gates can receive
 * arbitrary input, I tried out some examples; for each one of them, I use a variable to
 * denote each source gate and try to find the expression for each gate on paper. I noticed
 * a pattern, but didn't bother to prove it in the contest. Anyway, let's begin.
 * Let f(x) = (dp1[v_1] * x + dp0[v_1])...(dp1[v_k] * x + dp0[v_k]), then
 * partial[i] = coefficient of x^i in f(x). Write f(x) as c_0 + c_1 * x + ... + c_k * x^k.
 * We have
 *          f'(x) = 1 * c_1 + 2 * c_2 * x + 3 * c_3 * x^2 + ... + k * c_k * x^{k-1},
 * which means
 *      dp[u] = c_1 + 2 * c_2 + ... + k * c_k
 *            = f'(1)
 *            = sum{ dp1[v_i] * prod{ (dp1[v_j] + dp0[v_j]) : j != i }  : 1 <= i <= k }
 *            = sum{ dp[v_i] * prod{ sub[v_j] : j != i } }.
 * Here, sub[v] is the num of comb of freely choosing all thresholds in the subtree of v.
 * This quantity can be easily found with DFS. The trick is pretty well-known, at least
 * in the Olympiad maths circle.
 * 
 * After doing some more examples on my scratch paper, I noticed that the formula we
 * obtained above implies that dp[1] is a linear combination of
 * dp[N], dp[N+1], ..., dp[N+M-1], i.e. the inputs. Nice! It remains to compute the
 * coefficient of each input in the final linear comb of dp[1], and use
 * a segment tree with lazy propagation.
 * 
 * Overall, I'd say this is a pretty interesting problem, at least with the parts of dp & 
 * maths. The portion of handling queries with seg tree + lazy is definitely a bit
 * mechanical, but still tests a contestant's data structure skills. Note that the MOD is
 * not a prime! Therefore, dp[u] cannot be sum{ dp[v_i] / sub[v_i] } * prod{ sub[v_i] },
 * cuz a multiplicative inverse may not exist under mod 1000002022. The issue can be
 * resolved by computing the suffix product and prefix product of sub[].
 * 
 * Time Complexity: O(N + (M + Q) * log(M))         (Full solution)
 * Implementation 1                 (DP, maths, segment tree, lazy propagation)
*/

#include <bits/stdc++.h>
#include "circuit.h"

typedef long long   ll;
typedef std::vector<int>  vec;

const ll MOD = 1000002022;


// Our toggleable linear combination segment tree (using lazy propagation) (lol)
class TogLincomb {
private:
    int m;
    std::vector<ll> pre_coeff;
    std::vector<ll> tree;
    std::vector<bool> lazy;     // whether the range has been lazily toggled
    inline void flip(int k, int i, int j) {
        tree[k] = (pre_coeff[j + 1] - pre_coeff[i]) - tree[k], lazy[k] = 1 - lazy[k];
        tree[k] = (tree[k] % MOD + MOD) % MOD;
    }
    inline void push(int k, int i, int j) {
        if (lazy[k]) {
            int mid = (i + j) / 2;
            flip(2 * k, i, mid);
            flip(2 * k + 1, mid + 1, j);
        }
        lazy[k] = false;
    }
    void _toggle(int l, int r, int k, int i, int j) {
        if (i > r || l > j || l > r)
            return;
        if (i == l && j == r) {
            flip(k, i, j);
            return;
        }
        int mid = (i + j) / 2;
        push(k, i, j);
        _toggle(l, std::min(r, mid), 2 * k, i, mid);
        _toggle(std::max(l, mid + 1), r, 2 * k + 1, mid + 1, j);
        tree[k] = tree[2 * k] + tree[2 * k + 1], tree[k] %= MOD;
    }
public:
    TogLincomb()    {}
    TogLincomb(int size, const std::vector<ll>& coeff) {
        m = size;
        pre_coeff.assign(m + 1, 0);
        for (int k = 0; k < m; k++)
            pre_coeff[k + 1] = pre_coeff[k] + coeff[k], pre_coeff[k + 1] %= MOD;
        tree.assign(4 * m, 0);
        lazy.assign(4 * m, false);
    }
    void toggle(int l, int r)   { _toggle(l, r, 1, 0, m - 1); }
    ll sum()    { return tree[1]; }
};

int n;
std::vector<vec> tree;
std::vector<ll> dp, sub;
TogLincomb seg_tree;

void pre_comp(int k) {
    sub[k] = 1;
    if (k >= n)
        return;
    for (int child : tree[k]) {
        pre_comp(child);
        sub[k] *= sub[child], sub[k] %= MOD;
    }
    sub[k] *= int(tree[k].size()), sub[k] %= MOD;
}

void compute(int k) {
    int c = tree[k].size();
    std::vector<ll> prefix(c + 1), suffix(c + 1);
    prefix[0] = suffix[c] = 1;
    for (int i = 0, j = c - 1; i < c; i++, j--) {
        prefix[i + 1] = prefix[i] * sub[tree[k][i]] % MOD;
        suffix[j] = suffix[j + 1] * sub[tree[k][j]] % MOD;
    }
    for (int i = 0; i < c; i++) {
        int child = tree[k][i];
        dp[child] = dp[k] * prefix[i] % MOD * suffix[i + 1] % MOD;
        compute(child);
    }
}

void init(int N, int m, vec P, vec A) {
    n = N;
    tree.assign(n + m, vec());
    for (int k = 1; k < n + m; k++)
        tree[P[k]].push_back(k);
    
    sub.resize(n + m);
    pre_comp(0);
    dp.resize(n + m);
    dp[0] = 1;
    compute(0);
    seg_tree = TogLincomb(m, std::vector<ll>(dp.begin() + n, dp.end()));
    for (int k = 0; k < m; k++) {
        if (A[k])
            seg_tree.toggle(k, k);
    }
}

int count_ways(int l, int r) {
    seg_tree.toggle(l - n, r - n);
    return int(seg_tree.sum());
}
#Verdict Execution timeMemoryGrader output
Fetching results...
#Verdict Execution timeMemoryGrader output
Fetching results...
#Verdict Execution timeMemoryGrader output
Fetching results...
#Verdict Execution timeMemoryGrader output
Fetching results...
#Verdict Execution timeMemoryGrader output
Fetching results...
#Verdict Execution timeMemoryGrader output
Fetching results...
#Verdict Execution timeMemoryGrader output
Fetching results...
#Verdict Execution timeMemoryGrader output
Fetching results...