ATCoder Regular Contest 171 A~E

A.No Attacking(贪心)

题意:

有一个 N N N N N N列的棋盘。用 ( i , j ) (i,j) (i,j)表示从上往下数第 i i i行、从左往右数第 j j j列的方格。现在你要在棋盘上放置棋子。有两种类型的棋子,分别叫做车和兵。当满足以下条件时,放置的棋子被称为良好布局:

  • 每个方格上最多只能放置一个棋子。
  • 如果 ( i , j ) (i,j) (i,j)位置有一辆车,则对于所有 k ( 1 ≤ k ≤ N ) k(1≤k≤N) k1kN k ≠ j k≠j k=j ( i , k ) (i,k) (i,k)位置不能有其他棋子;同样地,如果 ( i , j ) (i,j) (i,j)位置有一辆车,则对于所有 k ( 1 ≤ k ≤ N ) k(1≤k≤N) k1kN k ≠ i k≠i k=i ( k , j ) (k,j) (k,j)位置不能有其他棋子;
  • 如果 ( i , j ) (i,j) (i,j)位置是一个兵,并且 i ≥ 2 i≥2 i2,则 ( i − 1 , j ) (i−1,j) (i1,j)位置不能有其他棋子。

是否可能将 A A A个车和 B B B个兵以良好布局方式全部放置在这个棋盘上?

分析:

题目的良好布局要求两个条件,先考虑解决其中一个条件。若 ( i , j ) (i,j) (i,j)位置是一个兵,则被约束的只有两个格子,如果是一个车,则被约束的是一行和一列。所以选择先把棋盘放满兵,接下来再去放车(全部放对角线上方便处理),放在没有兵的格子上,减少了 3 3 3个兵;放在一个原本有兵的格子上,则会减少 7 7 7个兵。所以先尽可能使用没有棋子的格子放车,没地方放了再放到原本有兵的格子上。

代码:

#include<bits/stdc++.h>

using namespace std;

int main() {
    int t;
    cin >> t;
    while (t--) {
        bool flag = false;
        int n, a, b;
        cin >> n >> a >> b;
        int all = (n) * (n / 2 + n % 2);
        int cost = n / 2 + n % 2;
        int tmp = n / 2;

        if (a <= tmp) {
            all -= cost * a;
        } else {
            flag = true;
            cost -= a - tmp;
        }

        if (flag == false) {
            if (all >= b)
                cout << "Yes" << endl;
            else
                cout << "No" << endl;
        } else {
            if (cost < 0)
                cout << "No" << endl;
            else {
                if (cost * cost >= b)
                    cout << "Yes" << endl;
                else
                    cout << "No" << endl;
            }
        }
    }
    return 0;
}

B.Chmax(数学)

题意:

对于 ( 1 , 2 , … , N ) (1,2,…,N) (1,2,,N)的排列 P = ( P 1 , P 2 , … , P N ) P=(P_1,P_2,…,P_N) P=(P1,P2,,PN),我们通过以下过程定义 F ( P ) F(P) F(P):有一个序列 B = ( 1 , 2 , … , N ) B=(1,2,…,N) B=(1,2,,N)。只要存在整数 i i i满足 B i < P B i B_i\lt P_{B_i} Bi<PBi,执行以下操作:令 j j j是最小的满足 B i < P B i B_i\lt P_{B_i} Bi<PBi的整数 i i i。然后,用 P B j P_{B_j} PBj替换 B j B_j Bj

F ( P ) F(P) F(P)定义为此过程结束时的序列 B B B。(可以证明该过程在有限步骤后终止。)

给定长度为 N N N的序列 A = ( A 1 , A 2 , … , A N ) A=(A_1,A_2,…,A_N) A=(A1,A2,,AN)。有多少个 ( 1 , 2 , … , N ) (1,2,…,N) (1,2,,N)的排列 P P P满足 F ( P ) = A F(P)=A F(P)=A?答案对 998244353 998244353 998244353取模。

分析:

分析题面中的操作可以发现,其实是将所有满足 P i > i P_i>i Pi>i i → P i i→P_i iPi连边,得到若干条链,然后 B i B_i Bi即为 i i i所在链的最后一个节点。

显然,存在 A i < i A_i\lt i Ai<i时无解,存在 A i ≠ i A_i≠i Ai=i A j = i A_j=i Aj=i时也无解。每个 A i ≠ i A_i≠i Ai=i的位置填的数都唯一确定了(必须是下一个满足 A j = A i A_j=A_i Aj=Ai j j j),只需计算将剩下的数填入 P P P中,且满足 P i < i P_i\lt i Pi<i的方案数。具体方法为从小到大枚举 i i i,统计有多少个不超过 i i i的数还没有用过,若当前位置需要填数,就从这些数中选择一个填入。使用乘法原理统计即可得到答案。

代码:

#include <bits/stdc++.h>

typedef long long LL;
using namespace std;
const LL mod = 998244353;
const LL N = 2e5 + 10;

LL n, a[N], ans = 1, b[N], cnt, flag[N];


int main() {
    cin >> n;
    for (int i = 1; i <= n; i++) cin >> a[i];
    for (int i = 1; i <= n; i++) {
        if (a[i] < i || a[a[i]] != a[i]) {
            cout << 0 << endl;
            return 0;
        }
    }
    for (int i = n; i >= 1; i--) {
        if (!b[a[i]]) b[a[i]] = i;
        else {
            int tmp = a[i];
            a[i] = b[a[i]];
            b[tmp] = i;
            flag[a[i]] = true;
        }
    }
    for (int i = 1; i <= n; i++) {
        if (!flag[i]) cnt++;
        if (a[i] == i) {
            ans *= cnt;
            ans %= mod;
            cnt--;
        }
    }
    cout << ans << endl;
    return 0;
}

C.Swap on Tree(树形DP)

题意:

有一棵具有 N N N个顶点的树,编号从 1 1 1 N N N。第 i i i条边连接了顶点 u i u_i ui v i v_i vi。此外,还有 N N N个编号为 1 1 1 N N N的片段。最初,片段 i i i放置在顶点 i i i上。可以任意次数(可能为零)执行以下操作:

  • 选择一条边。让 u u u v v v分别是该边的端点,并交换顶点 u u u v v v上的片段。然后删除所选边。

a i a_i ai是位于顶点 i i i上的片段。完成操作后存在多少种不同可能的序列 ( a i , a 2 , . . . , a N ) (a_i,a_2,...,a_N) (ai,a2,...,aN)?答案对 998244353 998244353 998244353取模。

分析:

采用树形动态规划思路。当取一个根节点时,在不同子树之间交换的顶点最多只有 1 1 1个。因此进行如下动态规划: d p [ v ] [ k ] dp[v][k] dp[v][k]←顶点 v , k v,k v,k。复杂度为 O ( N 2 ) O(N^2) O(N2)

代码:

#include <bits/stdc++.h>

typedef long long LL;
using namespace std;
const LL mod = 998244353;

template<int mod>
struct Fp {
    LL val;
    constexpr Fp() : val(0) {}
    constexpr Fp(LL v) : val(v % mod) {
        if (val < 0)
            val += mod;
    }
    constexpr LL get() const { return val; }
    constexpr int get_mod() const { return mod; }
    constexpr Fp operator+() const { return Fp(*this); }
    constexpr Fp operator-() const { return Fp(0) - Fp(*this); }
    constexpr Fp operator+(const Fp &r) const { return Fp(*this) += r; }
    constexpr Fp operator-(const Fp &r) const { return Fp(*this) -= r; }
    constexpr Fp operator*(const Fp &r) const { return Fp(*this) *= r; }
    constexpr Fp operator/(const Fp &r) const { return Fp(*this) /= r; }
    constexpr Fp &operator+=(const Fp &r) {
        val += r.val;
        if (val >= mod)
            val -= mod;
        return *this;
    }
    constexpr Fp &operator-=(const Fp &r) {
        val -= r.val;
        if (val < 0)
            val += mod;
        return *this;
    }
    constexpr Fp &operator*=(const Fp &r) {
        val = val * r.val % mod;
        return *this;
    }
    constexpr Fp &operator/=(const Fp &r) {
        LL a = r.val, b = mod, u = 1, v = 0;
        while (b) {
            LL t = a / b;
            a -= t * b, swap(a, b);
            u -= t * v, swap(u, v);
        }
        val = val * u % mod;
        if (val < 0)
            val += mod;
        return *this;
    }
    constexpr Fp pow(LL n) const {
        Fp res(1), mul(*this);
        while (n > 0) {
            if (n & 1) res *= mul;
            mul *= mul;
            n >>= 1;
        }
        return res;
    }
    constexpr Fp inv() const {
        Fp res(1), div(*this);
        return res / div;
    }
    constexpr bool operator==(const Fp &r) const {
        return this->val == r.val;
    }
    constexpr bool operator!=(const Fp &r) const {
        return this->val != r.val;
    }
    constexpr Fp &operator++() {
        ++val;
        if (val >= mod)
            val -= mod;
        return *this;
    }
    constexpr Fp &operator--() {
        if (val == 0)
            val += mod;
        --val;
        return *this;
    }
    constexpr Fp operator++(int) const {
        Fp res = *this;
        ++*this;
        return res;
    }
    constexpr Fp operator--(int) const {
        Fp res = *this;
        --*this;
        return res;
    }
    friend constexpr istream &operator>>(istream &is, Fp<mod> &x) {
        is >> x.val;
        x.val %= mod;
        if (x.val < 0)
            x.val += mod;
        return is;
    }
    friend constexpr ostream &operator<<(ostream &os, const Fp<mod> &x) {
        return os << x.val;
    }
    friend constexpr Fp<mod> pow(const Fp<mod> &r, LL n) {
        return r.pow(n);
    }
    friend constexpr Fp<mod> inv(const Fp<mod> &r) {
        return r.inv();
    }
};

template<class mint>
struct BiCoef {
    vector<mint> fact_, inv_, finv_;
    constexpr BiCoef() {}
    constexpr BiCoef(int n) : fact_(n, 1), inv_(n, 1), finv_(n, 1) {
        init(n);
    }
    constexpr void init(int n) {
        fact_.assign(n, 1), inv_.assign(n, 1), finv_.assign(n, 1);
        int mod = fact_[0].get_mod();
        for (int i = 2; i < n; i++) {
            fact_[i] = fact_[i - 1] * i;
            inv_[i] = -inv_[mod % i] * (mod / i);
            finv_[i] = finv_[i - 1] * inv_[i];
        }
    }
    constexpr mint com(int n, int k) const {
        if (n < k || n < 0 || k < 0)
            return 0;
        return fact_[n] * finv_[k] * finv_[n - k];
    }
    constexpr mint fact(int n) const {
        if (n < 0)
            return 0;
        return fact_[n];
    }
    constexpr mint inv(int n) const {
        if (n < 0)
            return 0;
        return inv_[n];
    }
    constexpr mint finv(int n) const {
        if (n < 0)
            return 0;
        return finv_[n];
    }
};

using mint = Fp<mod>;

int main() {
    int N;
    cin >> N;
    vector<vector<int>> G(N);
    for (int i = 0; i < N - 1; ++i) {
        int u, v;
        cin >> u >> v;
        --u, --v;
        G[u].push_back(v);
        G[v].push_back(u);
    }
    BiCoef<mint> bc(N + 1);
    vector<int> siz(N, 0);
    vector<vector<mint>> dp(N, vector<mint>(N, 0));
    vector<mint> dpsum(N, 0);
    auto rec = [&](auto rec, int v, int p) -> void {
        int cnt = 0;
        for (auto ch: G[v]) {
            if (ch == p)
                continue;
            rec(rec, ch, v);
            ++cnt;
        }
        siz[v] = 1;
        vector<mint> dp2(cnt + 2, 0);
        dp2[1] = 1;
        for (auto ch: G[v]) {
            if (ch == p)
                continue;
            siz[v] += siz[ch];

            vector<mint> nex(cnt + 2, 0);
            for (int k = 0; k <= cnt + 1; ++k) {
                nex[k] += dp2[k] * dpsum[ch];
                for (int l = 1; l <= siz[ch]; ++l) {
                    if (k + 1 <= cnt + 1) nex[k + 1] += dp2[k] * dp[ch][l] * l;
                }
            }
            swap(dp2, nex);
        }
        dpsum[v] = 0;
        for (int k = 1; k <= cnt + 1; ++k) {
            dp[v][k] = dp2[k] * bc.fact(k - 1);
            dpsum[v] += dp[v][k];
        }
    };
    rec(rec, 0, -1);
    cout << dpsum[0] << endl;
    return 0;
}

D.Rolling Hash(着色问题)

题意:

给定非负整数 P P P B B B P P P是素数, 1 ≤ B ≤ P − 1 1≤B≤P−1 1BP1。对于一个非负整数序列 X = ( x 1 , x 2 , … , x n ) X=(x_1,x_2,…,x_n) X=(x1,x2,,xn),哈希值 h a s h ( X ) hash(X) hash(X)定义如下。 h a s h ( X ) = ( ∑ i = 1 n x i B n − i ) m o d    P hash(X)=(\sum\limits_{i=1}^{n}x_i B^{n-i})\mod P hash(X)=(i=1nxiBni)modP。给定 M M M对整数 ( L 1 , R 1 ) (L_1,R_1) (L1,R1), ( L 2 , R 2 ) (L_2,R_2) (L2,R2),…, ( L M , R M ) (L_M,R_M) (LM,RM)。是否存在长度为 N N N的非负整数序列 A = ( A 1 , A 2 , … , A N ) A=(A_1,A_2,…,A_N) A=(A1,A2,,AN)满足以下条件:

  • 对于所有的 i ( 1 ≤ i ≤ M ) i(1≤i≤M) i(1iM),满足以下条件:设 s s s是由取出 A A A中第 L i L_i Li到第 R i R_i Ri元素得到的序列 ( A L i , A L i + 1 , … , A R i ) (A_{L_i},A_{L_i+1},…,A_{R_i}) (ALi,ALi+1,,ARi)。那么, h a s h ( s ) ≠ 0 hash(s)≠0 hash(s)=0.

分析:

首先,重新表述问题条件。定义 B ′ ≡ B − 1 B^′≡B−1 BB1,定义数列 A A A C i = A i B ′ i C_i=A_i B^{′i} Ci=AiBi。进一步定义 C C C的和为 S S S。这时候,可以发现问题条件等价于对每个 ( L i , R i ) (L_i,R_i) (Li,Ri),满足 S R i + 1 S_{R_i+1} SRi+1KaTeX parse error: Unknown accent ' ̸' at position 1: ≢̲̲ S L i S_{L_i} SLi ( m o d    P ) (\mod P) (modP)。因为在 m o d    P \mod P modP下,与 B B B进行乘除仍保持等价性。特别地,在 P > N P>N P>N时总会成立。例如即使对所有区间都有限制条件,只需取 S 0 ≡ 0 S_0≡0 S00 S 1 ≡ 1 S_1≡1 S11 S 2 ≡ 2 S_2≡2 S22 S N ≡ N S_N≡N SNN即可将满足要求的数列 A A A很容易逆推得出。

一般情况下,数列 S S S和数列 A A A之间仍然存在一对一关系不变。因此可以判断是否可能按照以下条件分配各个值来满足 S 0 , S 1 , … , S N S_0,S_1,…,S_N S0,S1,,SN:将序列 S i S_i Si中每个值设定为大于等于 0 0 0且小于等于 P − 1 P−1 P1;同时设定 S 0 ≡ 0 S_0≡0 S00;并确保对每个 ( L i , R i ) (L_i,R_i) (Li,Ri) S R i + 1 ≢ S L i m o d    P S_{R_i+1}≢S_{L_i} \mod P SRi+1SLimodP成立。
仔细想想就会发现这其实是一个着色问题。需要计算由顶点 0 , 1 , … , N 0,1,…,N 0,1,,N组成的 M M M条边 ( L i , R i + 1 ) (L_i,R_i+1) (Li,Ri+1)构成的图的着色数,并判断其是否小于等于 P P P

代码:

#include <bits/stdc++.h>

typedef long long LL;
using namespace std;
const LL mod = 998244353;

template<int mod>
struct Fp {
    LL val;
    constexpr Fp() : val(0) {}
    constexpr Fp(LL v) : val(v % mod) {
        if (val < 0) val += mod;
    }
    constexpr LL get() const { return val; }
    constexpr int get_mod() const { return mod; }
    constexpr Fp operator+() const { return Fp(*this); }
    constexpr Fp operator-() const { return Fp(0) - Fp(*this); }
    constexpr Fp operator+(const Fp &r) const { return Fp(*this) += r; }
    constexpr Fp operator-(const Fp &r) const { return Fp(*this) -= r; }
    constexpr Fp operator*(const Fp &r) const { return Fp(*this) *= r; }
    constexpr Fp operator/(const Fp &r) const { return Fp(*this) /= r; }
    constexpr Fp &operator+=(const Fp &r) {
        val += r.val;
        if (val >= mod)
            val -= mod;
        return *this;
    }
    constexpr Fp &operator-=(const Fp &r) {
        val -= r.val;
        if (val < 0)
            val += mod;
        return *this;
    }
    constexpr Fp &operator*=(const Fp &r) {
        val = val * r.val % mod;
        return *this;
    }
    constexpr Fp &operator/=(const Fp &r) {
        LL a = r.val, b = mod, u = 1, v = 0;
        while (b) {
            LL t = a / b;
            a -= t * b, swap(a, b);
            u -= t * v, swap(u, v);
        }
        val = val * u % mod;
        if (val < 0)
            val += mod;
        return *this;
    }
    constexpr Fp pow(LL n) const {
        Fp res(1), mul(*this);
        while (n > 0) {
            if (n & 1)
                res *= mul;
            mul *= mul;
            n >>= 1;
        }
        return res;
    }
    constexpr Fp inv() const {
        Fp res(1), div(*this);
        return res / div;
    }
    constexpr bool operator==(const Fp &r) const {
        return this->val == r.val;
    }
    constexpr bool operator!=(const Fp &r) const {
        return this->val != r.val;
    }
    constexpr Fp &operator++() {
        ++val;
        if (val >= mod)
            val -= mod;
        return *this;
    }
    constexpr Fp &operator--() {
        if (val == 0)
            val += mod;
        --val;
        return *this;
    }
    constexpr Fp operator++(int) const {
        Fp res = *this;
        ++*this;
        return res;
    }
    constexpr Fp operator--(int) const {
        Fp res = *this;
        --*this;
        return res;
    }
    friend constexpr istream &operator>>(istream &is, Fp<mod> &x) {
        is >> x.val;
        x.val %= mod;
        if (x.val < 0)
            x.val += mod;
        return is;
    }
    friend constexpr ostream &operator<<(ostream &os, const Fp<mod> &x) {
        return os << x.val;
    }
    friend constexpr Fp<mod> pow(const Fp<mod> &r, LL n) {
        return r.pow(n);
    }
    friend constexpr Fp<mod> inv(const Fp<mod> &r) {
        return r.inv();
    }
};

int chromatic_number(const vector<vector<int>> &G) {
    using mint = Fp<mod>;
    int n = (int) G.size();
    vector<int> neighbor(n, 0);
    for (int i = 0; i < n; ++i) {
        int S = (1 << i);
        for (int j = 0; j < n; ++j) if (G[i][j]) S |= (1 << j);
        neighbor[i] = S;
    }
    vector<int> I(1 << n);
    I[0] = 1;
    for (int S = 1; S < (1 << n); ++S) {
        int v = __builtin_ctz(S);
        I[S] = I[S & ~(1 << v)] + I[S & ~neighbor[v]];
    }
    int low = 0, high = n;
    while (high - low > 1) {
        int mid = (low + high) >> 1;

        mint g = 0;
        for (int S = 0; S < (1 << n); ++S) {
            if ((n - __builtin_popcount(S)) & 1)
                g -= mint(I[S]).pow(mid);
            else
                g += mint(I[S]).pow(mid);
        }
        if (g != 0)
            high = mid;
        else low = mid;
    }
    return high;
}

int main() {
    LL P, B, N, M;
    cin >> P >> B >> N >> M;
    vector<vector<int>> G(N + 1, vector<int>(N + 1, 0));
    for (int i = 0; i < M; ++i) {
        int l, r;
        cin >> l >> r;
        --l;
        G[l][r] = G[r][l] = 1;
    }
    if (chromatic_number(G) <= P)
        cout << "Yes" << endl;
    else
        cout << "No" << endl;
    return 0;
}

E.Rookhopper’s Tour

题意:

有一个 N N N N N N列的网格。用 ( i , j ) (i,j) (i,j)表示从上到下第 i i i行,从左到右第 j j j列的单元格。此外,有一颗黑色石头和 M M M颗白色石头。你将使用这些物品进行游戏。以下是规则:最初,你将黑色石头放在 ( A , B ) (A,B) (A,B)处。然后,你将每个白色石头放在网格的某个单元格上。注意:

  • 不能把白色石头放在 ( A , B ) (A,B) (A,B)处;
  • 每行最多只能放置一个白色石头;
  • 每列最多只能放置一个白色石头。

然后,直到无法继续操作为止,执行以下操作之一:

  • 假设黑色石头位于 ( i , j ) (i,j) (i,j),执行以下四种操作之一:
  1. 如果 ( i , k ) (i,k) (i,k)处有一颗白色石头且 ( j < k ) (j\lt k) (j<k),移除该白色石头并将黑色石头移动至 ( i , k + 1 ) (i,k+1) (i,k+1)
  2. 如果 ( i , k ) (i,k) (i,k)处有一颗白色石头且 ( j > k ) (j>k) (j>k),移除该白色石头并将黑色石头移动至 ( i , k − 1 ) (i,k−1) (i,k1);
  3. 如果 ( k , j ) (k,j) (k,j)处有一颗白色石头且 ( i < k ) (i\lt k) (i<k),移除该白色石头并将黑色石头移动至 ( k + 1 , j ) (k+1,j) (k+1,j);
  4. 如果 ( k , j ) (k,j) (k,j)处有一颗白色石头且 ( i > k ) (i>k) (i>k),移除该白色石头并将黑色石头移动至 ( k − 1 , j ) (k−1,j) (k1,j)

在这里,如果要移动黑色石头的单元格不存在,则无法进行此操作。

当你完成操作时,只有满足以下所有条件时才能赢得游戏;否则你输了:

  • 网格中的所有白色石头都被移除;
  • 黑色石头放在 ( A , B ) (A,B) (A,B)处。

通过执行操作,在多少种初始放置下可以赢得游戏?答案对998244353取模。

分析:

考虑白色石头的放置必须满足胜利条件的情况。首先,由于同一列中不能有多个白色石头,黑色石头必须按以下模式移动:( a a a类):垂直->水平->垂直->…->垂直->水平,或者( b b b类):水平->垂直->水平->…->水平->垂直。因此, M M M必须是偶数。

此外,可以证明对于满足条件的放置,只有一种类型的移动( a a a)或( b b b)是可能的。因此我们只需要计算每种类型的移动次数。考虑可以进行( a a a类)移动的放置方法,同时这种思路对( b b b类)也适用。我们考虑放置的特性。例如, 如果第一步向下走, 第二步向右走, 那么这些步骤如下所示: 跳过位于 ( x , B ) (x,B) (x,B)处的白色石头到达 ( x + 1 , B ) (x+1,B) (x+1,B), 然后跳过位于 ( x + 1 , y ) (x+1,y) (x+1,y)处的白色石头到达 ( x + 1 , y + 1 ) (x+1,y+1) (x+1,y+1)。这意味着在 x x x行和 ( x + 1 ) (x+1) (x+1)行都放置了石头。

类似地,在一般情况下下列关系成立(其中将第 n n n步视为模 M M M):设 n n n是奇数,则在第 n n n次和 ( n + 1 ) (n+1) (n+1)次移动中移除的白色石头被放置在相邻的行上。此外,如果黑色石头从上方来,则第 n n n次移动中的石头位于比 ( n + 1 ) (n+1) (n+1)次移动中的石头更高的行;如果它从下方来,则位于较低行。

n n n是偶数时,列也满足类似关系。因此,可以将石头的摆放解释为交替占据两行和两列的操作。

基于这些特性进行计数。简单起见, 首先考虑没有条件要求起始点(和结束点)是 ( A , B ) (A,B) (A,B)的情况。 所有放置操作都可以表示为交替占据两行和两列的操作。相反地,如果固定行和列的顺序被确定,那么对应的起始点和白色石头的放置就是唯一确定的。这种一对一的对应关系使得问题可以简化为计算要占用行和列的顺序。行和列排序方式的数量可以用二项式系数表示,并且可以算出。

起始点为 ( A , B ) (A,B) (A,B)的条件会对如何占用行和列施加以下限制:在第 ( M − 1 ) (M-1) (M1)次移动中移除的白色石头要么占据 A A A行和 ( A + 1 ) (A+1) (A+1)行,要么占据 ( A − 1 ) (A-1) (A1)行和 A A A行(哪些行被占据取决于第 ( M − 2 ) (M-2) (M2)次移动中移除白色石头位置是否在 A A A行之上或之下)。在第 M M M次和第 1 1 1次移动中移除的白色石头要么占据 B B B列和 ( B + 1 ) (B+1) (B+1)列,要么占据 ( B − 1 ) (B-1) (B1)列和 B B B列(哪些列被占据取决于第 ( M − 1 ) (M-1) (M1)次移动中移除白色石头位置是否在 B B B列右侧或左侧)。

该问题要求我们计算满足这些条件时行与列被占用的方式数量。通过固定第 ( M − 2 ) (M-2) (M2)次移动中移除的白色石头与 A A A行之间以及第 ( M − 1 ) (M-1) (M1)次移动中移除的白色石头与 B B B列之间的关系,总共有 4 4 4种情况,分情况累加可以得出答案。

代码:

#include<bits/stdc++.h>

typedef long long LL;
using namespace std;
const LL mod = 998244353;
const LL N = 2e5 + 10;

LL modinv(LL a, LL mod) {
    if (a < 0) a %= mod;
    if (a < 0) a += mod;
    if (a >= mod) a %= mod;
    assert(a);
    LL x = 0, y = 1, u = 1, v = 0, b = mod;
    while (b != 0) {
        LL q = a / b;
        a %= b;
        swap(a, b);
        u -= q * x;
        swap(u, x);
        v -= q * y;
        swap(y, v);
    }
    if (u > 0)
        return u;
    else return u + mod;
}

vector<LL> fact;
vector<LL> invfact;

void set_fact(LL n, LL mod) {
    fact.resize(n + 1, 1);
    invfact.resize(n + 1, 1);
    for (LL i = 2; i <= n; i++) {
        fact[i] = fact[i - 1] * i % mod;
    }
    invfact[n] = modinv(fact[n], mod);
    for (LL i = n - 1; i >= 2; i--) {
        invfact[i] = invfact[i + 1] * (i + 1) % mod;
    }
    return;
}

LL comb(LL n, LL k, LL mod) {
    if (k > n || k < 0) return 0;
    if (k == n || k == 0) return 1;
    return fact[n] * invfact[n - k] % mod * invfact[k] % mod;
}

int main() {
    int N, M, A, B;
    LL ans = 0;
    cin >> N >> M >> A >> B;
    set_fact(N + 10, mod);
    if (M % 2) {
        cout << 0 << endl;
        return 0;
    }
    LL x = 0;
    LL y = 0;
    LL coef = modinv(M / 2 - 1, mod);
    for (int a = 0; a <= M / 2 - 1; a++) {
        int b = M / 2 - 1 - a;
        int l = A - 1, r = N - A - 1;
        if (a * 2 <= l && b * 2 <= r) {
            LL summand = comb(l - a, a, mod) * comb(r - b, b, mod) % mod;
            if (b > 0) x += summand * fact[M / 2 - 1] % mod * b % mod * coef % mod;
        }
        l = A - 2, r = N - A;
        if (a * 2 <= l && b * 2 <= r) {
            LL summand = comb(l - a, a, mod) * comb(r - b, b, mod) % mod;
            if (a > 0) x += summand * fact[M / 2 - 1] % mod * a % mod * coef % mod;
        }
        int l1 = B - 1, r1 = N - B - 1;
        if (a * 2 <= l1 && b * 2 <= r1) {
            LL summand = comb(l1 - a, a, mod) * comb(r1 - b, b, mod) % mod;
            if (b > 0) y += summand * fact[M / 2 - 1] % mod * b % mod * coef % mod;
        }
        l1 = B - 2, r1 = N - B;
        if (a * 2 <= l1 && b * 2 <= r1) {
            LL summand = comb(l1 - a, a, mod) * comb(r1 - b, b, mod) % mod;
            if (a > 0) y += summand * fact[M / 2 - 1] % mod * a % mod * coef % mod;
        }
    }
    x %= mod;
    y %= mod;
    ans += x * y % mod;
    ans *= 2;
    ans %= mod;
    cout << ans << endl;
    return 0;
}

赛后交流

在比赛结束后,会在交流群中给出比赛题解,同学们可以在赛后查看题解进行补题。

群号: 704572101,赛后大家可以一起交流做题思路,分享做题技巧,欢迎大家的加入。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值