Solution Set【2024.2.21】

[ARC162C] Mex Game on Tree

可以发现若 Bob 在某个节点填了值 \(K\),那么会直接导致其根链上的所有节点均不可能满足要求。

因此若某个节点的子树内有不小于两个空位,那么 Alice 一定无法使该子树满足要求。

若某节点子树内有一个空位且可以通过填上这一空位使其合法,那么 Alice 可以在第一次操作中操作该节点进而胜利。

若某节点子树内不存在空位且 mex 值合法,那么 Alice 胜利。

反之若 Alice 操作某个空位,Bob 只需要找到与这个空位的根链交集最多的空位并填写 \(K\) 即可。可以发现 Bob 选择的节点一定可以覆盖当前状态下 Alice 选择的节点的根链上所有子树内有空位的节点,进而 Bob 必胜。

Code
#include <bits/stdc++.h>

typedef long long valueType;
typedef std::vector<valueType> ValueVector;
typedef std::vector<ValueVector> ValueMatrix;
typedef std::vector<bool> bitset;

void Solve() {
    valueType N, K;

    std::cin >> N >> K;

    ValueVector Father(N + 1), A(N + 1);

    for (valueType i = 2; i <= N; ++i)
        std::cin >> Father[i];

    for (valueType i = 1; i <= N; ++i)
        std::cin >> A[i];

    ValueMatrix SubTree(N + 1);

    for (valueType i = N; i >= 2; --i) {
        SubTree[i].push_back(i);

        SubTree[Father[i]].insert(SubTree[Father[i]].end(), SubTree[i].begin(), SubTree[i].end());
    }

    SubTree[1].push_back(1);

    for (valueType x = 1; x <= N; ++x) {
        bitset Bucket(N + 1, false);

        valueType LowerCount = 0, EditableCount = 0;

        for (auto const &v : SubTree[x]) {
            if (A[v] == -1) {
                ++EditableCount;
            } else if (!Bucket[A[v]]) {
                Bucket[A[v]] = true;

                if (A[v] < K)
                    ++LowerCount;
            }
        }

        if (Bucket[K])
            continue;

        if ((LowerCount == K && EditableCount <= 1) || (LowerCount == K - 1 && EditableCount == 1)) {
            std::cout << "Alice" << std::endl;

            return;
        }
    }

    std::cout << "Bob" << std::endl;
}

int main() {
    std::ios::sync_with_stdio(false);
    std::cin.tie(nullptr);
    std::cout.tie(nullptr);

    valueType T;

    std::cin >> T;

    for (valueType testcase = 1; testcase <= T; ++testcase)
        Solve();

    std::cout << std::flush;

    return 0;
}

[ARC144D] AND OR Equation

首先我们可以转化题目限制,设 \(U = \left\{1, 2, 3, \cdots, N\right\}\),那么题目限制实际上为:

对于任意集合 \(S, T \subseteq U\),满足 \(f(S) + f(T) = f(S \cap T) + f(S \cup T)\)

考虑设 \(c = f(\varnothing), x_i = f(\left\{i\right\}) - c\)

我们不难发现,一个多元组 \(\left(c, x_1, x_2, \cdots, x_N\right)\)\(f\) 一一对应。具体的,取 \(f(S) = c + \sum\limits_{i \in S} x_i\) 即可。

因此我们对合法的多元组计数即可。

考虑多元组合法的充要条件,即为:

\[\forall S \subseteq U \rightarrow 0 \le c + \sum\limits_{i \in S} x_i \le K \]

\(s^{-} = \sum\limits_{i = 1}^{N} \left[x_i < 0\right] x_i, s^{+} = \sum\limits_{i = 1}^{N} \left[x_i > 0\right] x_i\),那么我们有:

\[\forall S \subseteq U \rightarrow s^{-} \le \sum\limits_{i \in S} x_i \le S^{+} \]

因此上述条件可以改写为:

\[\begin{aligned} 0 &\le c + s^{-} \\ c + s^{+} &\le K \end{aligned}\]

\[-s^{-} \le c \le K - s^{+} \]

因此若 \(s^{-}\)\(s^{+}\) 确定,那么 \(c\) 的取值有 \(\max\limits\left\{0, K + 1 - s^{+} + s^{-}\right\}\) 种。考虑到 \(\sum\limits_{i = 1}^{N} \left\lvert x_i \right\rvert = s^{+} - s^{-}\),那么 \(c\) 的取值种数可以表达为 \(\max\limits\left\{0, K + 1 - \sum\limits_{i = 1}^{N} \left\lvert x_i \right\rvert\right\}\)

那么我们现在的问题转化为了对于所有满足 \(\sum\limits_{i = 1}^{N} \left\lvert x_i \right\rvert \le K\) 的序列 \(x\),求出 \(K + 1 - \sum\limits_{i = 1}^{N} \left\lvert x_i \right\rvert\) 的和。

首先考虑处理掉绝对值。不妨枚举序列中的非 \(0\) 数个数 \(n\),后枚举其正负性,这部分的方案数为:

\[\dbinom{N}{n} 2^n \]

进而我们的问题就转化为了对于所有长度为 \(n\)正整数序列 \(y\),要求 \(\sum\limits_{i = 1}^{n} y_i \le K\),设 \(s = \sum\limits_{\sum\limits_{i = 1}^{n} y_i}\),求 \(K + 1 - s\) 的和。即 \(g(n, s)\) 表示长度为 \(n\) 且和为 \(s\) 的正整数序列数量,那么我们要求的实际上为:

\[\begin{aligned} &\sum\limits_{s = 1}^{K} g(n, s) \times \left(K + 1 - s\right) \\ =&\sum\limits_{s = 1}^{K} g(n, s) \sum\limits_{a = 1}^{K + 1 - s} 1 \\ \end{aligned}\]

考虑枚举 \(a + s\) 的和 \(s_a\),那么我们实际上要求的为:

\[\begin{aligned} &\sum\limits_{s_a = 1}^{K + 1} g(n + 1, s_a) \end{aligned}\]

\(b = K + 2 - s_a\),那么我们有 \(b \ge 1\),同时我们有 \(s + a + b = K + 2\),发现确定了 \(b\) 的取值即可确定出 \(s_a\) 的取值。因此我们将 \(b\) 拼入序列中是正确的。因此我们要求的为 \(g(n + 2, K + 2)\)。根据插板法,我们有:

\[g(n, m) = \dbinom{m - 1}{n - 1} \]

因此答案为:

\[\sum\limits_{n = 0}^{N} 2^n \dbinom{N}{n} \dbinom{K + 1}{n + 1} \]

将组合数转化为下降幂即可 \(\mathcal{O}(N)\) 计算。

Code
#include <bits/stdc++.h>

typedef long long valueType;
typedef std::vector<valueType> ValueVector;

namespace MODINT_WITH_FIXED_MOD {
    constexpr valueType MOD = 998244353;

    template<typename T1, typename T2>
    void Inc(T1 &a, T2 b) {
        a = a + b;

        if (a >= MOD)
            a -= MOD;
    }

    template<typename T1, typename T2>
    void Dec(T1 &a, T2 b) {
        a = a - b;

        if (a < 0)
            a += MOD;
    }

    template<typename T1, typename T2>
    T1 sum(T1 a, T2 b) {
        return a + b >= MOD ? a + b - MOD : a + b;
    }

    template<typename T1, typename T2>
    T1 sub(T1 a, T2 b) {
        return a - b < 0 ? a - b + MOD : a - b;
    }

    template<typename T1, typename T2>
    T1 mul(T1 a, T2 b) {
        return (long long) a * b % MOD;
    }

    template<typename T1, typename T2>
    void Mul(T1 &a, T2 b) {
        a = (long long) a * b % MOD;
    }

    template<typename T1, typename T2>
    T1 pow(T1 a, T2 b) {
        T1 result = 1;

        while (b > 0) {
            if (b & 1)
                Mul(result, a);

            Mul(a, a);
            b = b >> 1;
        }

        return result;
    }
} // namespace MODINT_WITH_FIXED_MOD

using namespace MODINT_WITH_FIXED_MOD;

class Inverse {
private:
    valueType N;
    ValueVector Inv;

public:
    explicit Inverse(valueType n) : N(n), Inv(n + 10, 0) {
        Inv[0] = 1;
        Inv[1] = 1;

        for (valueType i = 2; i <= N; ++i)
            Inv[i] = mul(MOD - MOD / i, Inv[MOD % i]);
    }

    valueType operator()(valueType i) const {
        if (i <= N)
            return Inv[i];

        return mul(MOD - MOD / i, (*this)(MOD % i));
    }
};

int main() {
    std::ios::sync_with_stdio(false);
    std::cin.tie(nullptr);
    std::cout.tie(nullptr);

    valueType N, K;

    std::cin >> N >> K;

    Inverse const Inv(N + 1);

    valueType product = (K + 1) % MOD;
    valueType Ans = product;

    for (valueType n = 1; n <= N && n <= K; ++n) {
        Mul(product, N - n + 1);
        Mul(product, Inv(n));
        Mul(product, 2);
        Mul(product, (K + 1 - n) % MOD);
        Mul(product, Inv(n + 1));

        Inc(Ans, product);
    }

    std::cout << Ans << std::endl;

    return 0;
}

[ARC148C] Lights Out on Tree

翻转某个点的策略为操作这个节点以及这个节点的所有儿子节点。若对于集合中某点 \(u\),若 \(u\) 的父亲节点也在集合中那么节点 \(u\) 会被操作两次,可以抵消进而减少两次操作。

Code
#include <bits/stdc++.h>

typedef long long valueType;
typedef std::vector<valueType> ValueVector;
typedef std::vector<bool> bitset;

int main() {
    std::ios::sync_with_stdio(false);
    std::cin.tie(nullptr);
    std::cout.tie(nullptr);

    valueType N, Q;

    std::cin >> N >> Q;

    ValueVector Father(N + 1, 0);
    ValueVector SonCount(N + 1, 0);

    for (valueType i = 2; i <= N; ++i) {
        std::cin >> Father[i];

        ++SonCount[Father[i]];
    }

    bitset Exist(N + 1, false);

    for (valueType q = 0; q < Q; ++q) {
        valueType M;

        std::cin >> M;

        ValueVector S(M);

        for (auto &x : S)
            std::cin >> x;

        valueType Ans = 0;

        for (auto const &x : S) {
            Exist[x] = true;

            Ans += SonCount[x] + 1;
        }

        for (auto const &x : S) {
            if (Exist[Father[x]])
                Ans -= 2;
        }

        std::cout << Ans << std::endl;

        for (auto const &x : S)
            Exist[x] = false;
    }

    return 0;
}

[ARC146D] >=<

考虑将限制转化为 \(A_i = x \Leftrightarrow A_j = y\)\(A_i > x \Leftrightarrow A_j > y\) 的形式。

具体的,对于给定限制 \(\left(P, X, Q, Y\right)\),可以将其转化为:

  • \(A_P = X \Leftrightarrow A_Q = Y\)
  • \(A_P > X \Leftrightarrow A_Q > Y\)

可以发现上述条件等价于:

  • \(A_P > X - 1 \Leftrightarrow A_Q > Y - 1\)
  • \(A_P > X \Leftrightarrow A_Q > Y\)

进而我们得到了 \(\mathcal{O}(K)\) 个形如 \(A_i > x \Leftrightarrow A_j > y\) 的限制。考虑如何得出和最小的序列。

考虑贪心求解:初始时将 \(A_i\) 均设为 \(1\),后依次考虑 \(A_i\) 触发的限制并更新 \(A_j\),后继续检查 \(A_j\) 的限制并更新其他元素。

将限制视作边,进行 bfs 即可。

若最终序列中存在元素值大于 \(M\) 则无解。

复杂度为 \(\mathcal{O}(N + K)\)

Code
#include <bits/stdc++.h>

typedef long long valueType;
typedef std::vector<valueType> ValueVector;
typedef std::tuple<valueType, valueType, valueType> ValueTuple;
typedef std::vector<ValueTuple> TupleVector;
typedef std::vector<TupleVector> TupleMatrix;
typedef std::queue<valueType> ValueQueue;

int main() {
    std::ios::sync_with_stdio(false);
    std::cin.tie(nullptr);
    std::cout.tie(nullptr);

    valueType N, M, K;

    std::cin >> N >> M >> K;

    ValueVector A(N + 1, 1);
    TupleMatrix Graph(N + 1);

    auto AddLimit = [&](valueType p, valueType x, valueType q, valueType y) {
        Graph[p].emplace_back(x, q, y);
        Graph[q].emplace_back(y, p, x);
    };

    for (valueType k = 0; k < K; ++k) {
        valueType p, x, q, y;

        std::cin >> p >> x >> q >> y;

        AddLimit(p, x, q, y);
        AddLimit(p, x - 1, q, y - 1);
    }

    for (valueType i = 1; i <= N; ++i)
        std::sort(Graph[i].begin(), Graph[i].end(), std::greater<>());

    ValueQueue Q;

    auto Update = [&](valueType p) {
        while (!Graph[p].empty() && std::get<0>(Graph[p].back()) < A[p]) {
            auto const &[x, q, y] = Graph[p].back();

            if (A[q] < y + 1) {
                A[q] = y + 1;

                Q.push(q);
            }

            Graph[p].pop_back();
        }
    };

    for (valueType i = 1; i <= N; ++i)
        Update(i);

    while (!Q.empty()) {
        valueType const x = Q.front();

        Q.pop();

        Update(x);
    }

    valueType Ans = 0;

    for (valueType i = 1; i <= N; ++i) {
        if (A[i] > M) {
            std::cout << -1 << std::endl;

            return 0;
        }

        Ans += A[i];
    }

    std::cout << Ans << std::endl;

    return 0;
}

[ARC146C] Even XOR

我们按增量法去考虑方案数,即考虑在一个合法集合的基础上添加哪些元素可以继续得到一个合法集合。

假设我们现在存在一个合法集合 \(S\),设集合 \(T \subseteq S\) 且满足 \(\left\lvert T \right\rvert\) 为奇数,设 \(X = \bigoplus\limits_{x \in T} x\)。那么显然 \(X\) 不能被添加进 \(S\) 集合。同时我们有 \(X \notin S \lor \left\lvert T \right\rvert = 1\),否则集合 \(T \setminus \left\{X \right\}\) 的大小为偶数,同时异或和为 \(0\),与 \(S\) 为合法集合的事实冲突。

因此我们在 \(S\) 集合的基础上可以添加的元素为 \(2^N - \left\lvert\left\{X \mid \bigoplus\limits_{x \in T} x, T \subseteq S \land \left\lvert T \right\rvert \text{is odd.}\right\}\right\rvert\),即所有可能值除去所有大小为奇数的子集的异或和的集合。同时可以发现,由于所有大小为 \(1\) 的子集 \(T\) 的异或和集合等于 \(S\) 集合,因此无需考虑集合元素不可重复的要求。

我们下面考虑如何计算 \(\left\lvert\left\{X \mid \bigoplus\limits_{x \in T} x, T \subseteq S \land \left\lvert T \right\rvert \text{is odd.}\right\}\right\rvert\),我们尝试证明对于任意两个满足大小为奇数的子集 \(T_1, T_2\),其元素异或和不同。考虑使用反证法,若存在两个大小为奇数的子集 \(T_1, T_2\) 满足 \(\bigoplus\limits_{x \in T_1} x = \bigoplus\limits_{x \in T_2} x\),那么集合 \(T_1 \cup T_2\) 的元素异或和为 \(0\) 且集合大小为 \(\left\lvert T_1 \right\rvert + \left\lvert T_2 \right\rvert - \left\lvert T_1 \cap T_2 \right\rvert\),不难发现其值为偶数,这与 \(S\) 为合法集合冲突,进而命题成立。

因此我们可以得出 \(\left\lvert\left\{X \mid \bigoplus\limits_{x \in T} x, T \subseteq S \land \left\lvert T \right\rvert \text{is odd.}\right\}\right\rvert\) 的值实际上为 \(S\) 中奇数子集的数量,根据二项式定理可以得出其值为 \(2^{\left\lvert S \right\rvert - 2}\),不难发现当 \(\left\lvert S \right\rvert = N + 2\) 时不能加入的元素数量为 \(2^N\),即可以加入的元素数量为 \(0\),因此集合大小上界为 \(\mathcal{O}(N)\) 级别,考虑以集合大小为状态进行递推。

具体的,设 \(f_i\) 表示大小为 \(i\) 的合法集合数量,边界状态为 \(f_0 = 1, f_1 = 2^N\),对于 \(i \ge 2\),我们有转移:

\[f_i = \frac{f_{i - 1} \times \left(2^N - 2^{i - 2}\right)}{i} \]

其中除去 \(i\) 是为了保证集合的无序性。

复杂度为 \(\mathcal{O}(N)\)

Code
#include <bits/stdc++.h>

typedef long long valueType;
typedef std::vector<valueType> ValueVector;

namespace MODINT_WITH_FIXED_MOD {
    constexpr valueType MOD = 998244353;

    template<typename T1, typename T2>
    void Inc(T1 &a, T2 b) {
        a = a + b;

        if (a >= MOD)
            a -= MOD;
    }

    template<typename T1, typename T2>
    void Dec(T1 &a, T2 b) {
        a = a - b;

        if (a < 0)
            a += MOD;
    }

    template<typename T1, typename T2>
    T1 sum(T1 a, T2 b) {
        return a + b >= MOD ? a + b - MOD : a + b;
    }

    template<typename T1, typename T2>
    T1 sub(T1 a, T2 b) {
        return a - b < 0 ? a - b + MOD : a - b;
    }

    template<typename T1, typename T2>
    T1 mul(T1 a, T2 b) {
        return (long long) a * b % MOD;
    }

    template<typename T1, typename T2>
    void Mul(T1 &a, T2 b) {
        a = (long long) a * b % MOD;
    }

    template<typename T1, typename T2>
    T1 pow(T1 a, T2 b) {
        T1 result = 1;

        while (b > 0) {
            if (b & 1)
                Mul(result, a);

            Mul(a, a);
            b = b >> 1;
        }

        return result;
    }
} // namespace MODINT_WITH_FIXED_MOD

using namespace MODINT_WITH_FIXED_MOD;

class Inverse {
private:
    valueType N;
    ValueVector Inv;

public:
    explicit Inverse(valueType n) : N(n), Inv(n + 10, 0) {
        Inv[0] = 1;
        Inv[1] = 1;

        for (valueType i = 2; i <= N; ++i)
            Inv[i] = mul(MOD - MOD / i, Inv[MOD % i]);
    }

    valueType operator()(valueType i) const {
        if (i <= N)
            return Inv[i];

        return mul(MOD - MOD / i, (*this)(MOD % i));
    }
};

int main() {
    std::ios::sync_with_stdio(false);
    std::cin.tie(nullptr);
    std::cout.tie(nullptr);

    valueType N;

    std::cin >> N;

    ValueVector Pow2(N + 1, 0);

    Pow2[0] = 1;
    for (valueType i = 1; i <= N; ++i)
        Pow2[i] = mul(Pow2[i - 1], 2);

    Inverse const Inv(N + 2);

    ValueVector F(N + 3, 0);

    F[0] = 1;
    F[1] = Pow2[N];

    for (valueType i = 2; i <= N + 2; ++i)
        F[i] = mul(Inv(i), mul(F[i - 1], sub(Pow2[N], Pow2[i - 2])));

    valueType Ans = 0;

    for (valueType i = 0; i <= N + 2; ++i)
        Inc(Ans, F[i]);

    std::cout << Ans << std::endl;

    return 0;
}

[ARC159C] Permutation Addition

\(S = \frac{N \times \left(N + 1\right)}{2}\),可以发现我们一定有 \(N \mid 2 \cdot S\)。因此我们可以得出有解的必要条件为 \(N \mid \sum A_i \lor N \mid \left(\sum A_i + S\right)\)。因此若 \(N \nmid \sum A_i\)\(N \mid \left(\sum A_i + S\right)\) 我们不妨随意进行一次操作进而得到 \(N \mid \sum A_i\) 的问题。

我们下面考虑在 \(N \mid \sum A_i\) 的限制下进行操作并逐渐使得序列值相同。根据上述分析,我们必须成对的进行操作才能维持 \(N \mid \sum A_i\) 的限制。

考虑若要不改变元素的值,那么我们第一次先任意操作,第二次取 \(p_{i, 2} = N + 1 - p_{i, 1}\) 即可。发现在这样的情况下每个元素值均增加 \(N + 1\)

同时我们的目标是将序列元素的值均操作为平均值,因此我们考虑每一次选取一个大于平均值的元素和一个小于平均值的元素。后通过调整排列使得大的元素增加的值为 \(N\),小的元素增加的值为 \(N + 2\)。设其分别为 \(x, y\),这可以通过改变排列为 \(p_{x, 1} = 1, p_{x, 2} = n - 1, p_{y, 1} = 2, p_{y, 2} = n\) 来实现。

因此我们可以通过若干次调整来使得序列满足条件。

Code
#include <bits/stdc++.h>

typedef long long valueType;
typedef std::vector<valueType> ValueVector;
typedef std::vector<ValueVector> ValueMatrix;

int main() {
    std::ios::sync_with_stdio(false);
    std::cin.tie(nullptr);
    std::cout.tie(nullptr);

    valueType N;

    std::cin >> N;

    ValueVector A(N);

    for (auto &a : A)
        std::cin >> a;

    valueType const S = N * (N + 1) / 2;

    valueType Sum = std::accumulate(A.begin(), A.end(), (valueType) 0);

    if ((Sum % N != 0) && ((Sum + S) % N != 0)) {
        std::cout << "No" << std::endl;

        return 0;
    }

    ValueVector Inc(N), Dec(N);

    for (valueType i = 0; i < N; ++i) {
        Inc[i] = i + 1;

        Dec[i] = N - i;
    }

    ValueMatrix Ans;

    if (Sum % N != 0) {
        Ans.push_back(Inc);

        for (valueType i = 0; i < N; ++i)
            A[i] += Inc[i];

        Sum += S;
    }

    assert(Sum % N == 0);
    assert(Sum == std::accumulate(A.begin(), A.end(), (valueType) 0));

    valueType const Average = Sum / N;

    ValueVector B = A;

    for (auto &x : B)
        x -= Average;

    while (true) {
        valueType X = -1, Y = -1;

        for (valueType i = 0; i < N; ++i) {
            if (B[i] < 0)
                X = i;
            else if (B[i] > 0)
                Y = i;
        }

        if (X == -1 && Y == -1)
            break;

        assert(X != -1 && Y != -1);

        ValueVector P1(N, -1), P2(N, -1);

        assert(X != Y);

        P1[Y] = 1;
        P1[X] = 2;
        P2[Y] = N - 1;
        P2[X] = N;

        valueType V1 = 2, V2 = N - 1;

        for (valueType i = 0; i < N; ++i) {
            if (P1[i] == -1)
                P1[i] = ++V1;

            if (P2[i] == -1)
                P2[i] = --V2;
        }

        ++B[X];
        --B[Y];

        Ans.push_back(P1);
        Ans.push_back(P2);
    }

    std::cout << "Yes" << std::endl;

    std::cout << Ans.size() << std::endl;

    for (auto const &P : Ans) {
        for (auto const &x : P)
            std::cout << x << ' ';

        std::cout << std::endl;
    }

    return 0;
}
posted @ 2024-02-21 21:11  User-Unauthorized  阅读(24)  评论(0编辑  收藏  举报