比赛链接

A

题解

知识点:数学。

\(x = 0\) 时,当且仅当 \(y = 0\) 可行。

\(x \neq 0\) 时,一定可行,答案为 \(|x-y|\)

时间复杂度 \(O(1)\)

空间复杂度 \(O(1)\)

代码

#include <bits/stdc++.h>
using namespace std;
using ll = long long;

int main() {
    std::ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
    string x, y;
    cin >> x >> y;
    ll X = 0, Y = 0;
    for (int i = 0;i < x.size();i++) (X <<= 1) |= x[i] - '0';
    for (int i = 0;i < y.size();i++) (Y <<= 1) |= y[i] - '0';

    if (X == 0) {
        if (Y == 0) cout << 0 << '\n';
        else cout << -1 << '\n';
    }
    else cout << abs(X - Y) << '\n';

    return 0;
}

B

题解

知识点:排列组合,计数dp。

注意到,最后的排列一定是一段小数 \([1,n]\) ,一段大数 \([n+1,2n]\) 交替。

考虑设 \(f_{i,j,0/1}\) 表示填了 \(i\) 个小数, \(j\) 个大数,最后一段是小数或大数段的合法方案数(注意这里没包括最后一个错误的牌),有转移方程:

if (k <= i) f[i][j][0] += f[i - k][j][1] * CNM::C(n - i + k, k);
if (k <= j) f[i][j][1] += f[i][j - k][0] * CNM::C(n - j + k, k);

其中 \(k\) 为最后一段数字的个数。

统计答案时,我们考虑每个位置猜对时产生的贡献。对于前 \(i\) 个小数, \(j\) 个大数猜对时的方案数量为:

\[(f_{i,j,0} + f_{i,j,1}) \cdot (2n-i-j)!
\]

此时的所有方案,一定会在第 \(i+j\) 的位置产生一张猜对的牌的贡献。

最后我们需要加上每个排列最后一张错误的牌,共 \((2n)! - (f_{n,n,0} + f_{n,n,1})\) 种方案会产生一张错误的牌。

时间复杂度 \(O(n^3)\)

空间复杂度 \(O(n^2)\)

代码

#include <bits/stdc++.h>
using namespace std;
using ll = long long;

struct Modint {
    static int P;

    int val;
    Modint(int _val = 0) :val(_val %P) { format(); }
    Modint(ll _val) :val(_val %P) { format(); }

    //if val in [-P,2P)
    Modint &format() {
        if (val < 0) val += P;
        if (val >= P) val -= P;
        return *this;
    }
    Modint inv()const { return qpow(*this, P - 2); }

    Modint &operator+=(const Modint &x) { val += x.val;return format(); }
    Modint &operator-=(const Modint &x) { val -= x.val;return format(); }
    Modint &operator*=(const Modint &x) { val = 1LL * val * x.val % P;return *this; }
    Modint &operator/=(const Modint &x) { return *this *= x.inv(); }
    friend Modint operator-(const Modint &x) { return { -x.val }; }
    friend Modint operator+(Modint a, const Modint &b) { return a += b; }
    friend Modint operator-(Modint a, const Modint &b) { return a -= b; }
    friend Modint operator*(Modint a, const Modint &b) { return a *= b; }
    friend Modint operator/(Modint a, const Modint &b) { return a /= b; }

    friend Modint qpow(Modint a, ll k) {
        Modint ans = 1;
        while (k) {
            if (k & 1) ans = ans * a;
            k >>= 1;
            a = a * a;
        }
        return ans;
    }

    friend istream &operator>>(istream &is, Modint &x) {
        ll _x;
        is >> _x;
        x = { _x };
        return is;
    }
    friend ostream &operator<<(ostream &os, const Modint &x) { return os << x.val; }
};
/// 自动取模整数,O(logk)快速幂、O(logP)逆元、O(1)运算

int P;
namespace CNM {
    const int N = 3e2 + 7;
    int c[N][N];
    void init(int n) {
        for (int i = 0;i <= n;i++)
            for (int j = 0;j <= i;j++)
                c[i][j] = 0 < j && j < i ? (c[i - 1][j - 1] + c[i - 1][j]) % P : 1;
    }
    int C(int n, int m) {
        if (n == m && m == -1) return 1; //* 隔板法特判
        if (n < m || m < 0) return 0;
        return c[n][m];
    }
}

int Modint::P = 1e9 + 7;
Modint f[307][307][2];
Modint fact[607];
bool solve() {
    int n;
    cin >> n >> P;

    Modint::P = P;
    CNM::init(n);
    fact[0] = 1;
    for (int i = 1;i <= 2 * n;i++) fact[i] = fact[i - 1] * i;
    for (int i = 0;i <= n;i++) for (int j = 0;j <= n;j++) f[i][j][0] = f[i][j][1] = 0;
    f[0][0][0] = f[0][0][1] = 1;
    Modint ans = 0;
    for (int i = 0;i <= n;i++) {
        for (int j = 0;j <= n;j++) {
            for (int k = 1;k <= n;k++) {
                if (k <= i) f[i][j][0] += f[i - k][j][1] * CNM::C(n - i + k, k);
                if (k <= j) f[i][j][1] += f[i][j - k][0] * CNM::C(n - j + k, k);
            }
            if (i + j > 0) ans += (f[i][j][0] + f[i][j][1]) * fact[2 * n - i - j];// 第i+j张牌正确时的贡献 = 至少在i+j前正确时的方案数 * 1
        }
    }
    ans += fact[2 * n]; // 所有排列都会有一张错误的牌
    ans -= f[n][n][0] + f[n][n][1]; // 除了全对的排列
    cout << ans << '\n';
    return true;
}

int main() {
    std::ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
    int t = 1;
    cin >> t;
    while (t--) {
        if (!solve()) cout << -1 << '\n';
    }
    return 0;
}

D

题解

知识点:位运算,数学。

注意到,最终只有全 \(0\)\(1\) 的情况才是合法的。因此考虑从这两种情况逆推,不妨考虑全 \(0\) 情况。

我们发现,无论如何操作,所有行都和第一行或者第一行取反一致(对列一样,这里只需要考虑行即可)。因此,一开始先判断矩阵是否合法,再进行操作次数的计算。

对于操作次数, 最小值为第一行的 \(0,1\) 最少数量加第一列的 \(0,1\) 最少数量。因为,我们先对行操作把一行对齐,此时所有行都是一样的,在对列操作即可。对行操作的次数,即第一列的 \(0,1\) 最少数量;之后,对列操作的次数,即第一行的 \(0,1\) 最少数量。

时间复杂度 \(O(n^2)\)

空间复杂度 \(O(n^2)\)

代码

#include <bits/stdc++.h>
using namespace std;
using ll = long long;

int a[2007][2007];
int main() {
    std::ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
    int n;
    cin >> n;
    for (int i = 1;i <= n;i++)
        for (int j = 1;j <= n;j++) {
            char ch;
            cin >> ch;
            a[i][j] = ch == '1';
        }
    for (int i = 1;i <= n;i++) {
        bool ok = 1, f = a[i][1] == a[1][1];
        for (int j = 1;j <= n;j++) ok &= (a[i][j] == a[1][j]) == f;
        if (!ok) {
            cout << -1 << '\n';
            return 0;
        }
    }
    array<int, 4> cnt{};
    for (int i = 1;i <= n;i++) {
        if (a[1][i]) cnt[1]++;
        else cnt[0]++;
        if (a[i][1]) cnt[2 + 1]++;
        else cnt[2 + 0]++;
    }
    cout << min(cnt[0], cnt[1]) + min(cnt[2], cnt[3]) << '\n';
    return 0;
}

H

题解

知识点:数论。

显然,操作可以使得数字任意分配,但总和不变,因此考虑总和能否构造为质数序列。

哥德巴赫猜想:对于一个大于等于 \(4\) 的偶数,总能分为两个质数之和。(人类探索范围没有反例)

分类讨论:

  1. \(n=1\) 时,判断是否为质数即可。

  2. \(n=2\) 时,若 \(sum < 4\) 则无解。

    否则,若 \(sum\) 是奇数,那么 \(sum-2\) 是质数即可;若 \(sum\) 是偶数,根据哥德巴赫猜想一定有解。

  3. \(n \geq 3\) 时,若 \(sum < 2n\) 则无解。

    否则,若 \(sum\) 是奇数,前面放 \(n-3\)\(2\)\(1\)\(3\) ,最后还剩大于等于 \(4\) 的偶数,根据哥德巴赫猜想一定有解 ;若 \(sum\) 是偶数,根据哥德巴赫猜想一定有解。

    综上 \(sum \geq 2n\) 一定有解。

时间复杂度 \(O(n)\)

空间复杂度 \(O(1)\)

代码

#include <bits/stdc++.h>
using namespace std;
using ll = long long;

bool isPrime(int n) {
    if (n == 2) return 1;
    if (n == 1) return 0;
    for (int i = 2;i * i <= n;i++) if (!(n % i)) return 0;
    return 1;
}
/// 试除法,O(n^(1/2)),枚举[1,sqrt(n)]作为质因子判断素数

int a[1007];
int main() {
    std::ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
    int n;
    cin >> n;
    ll sum = 0;
    for (int i = 1;i <= n;i++) cin >> a[i], sum += a[i];
    if (n == 1) {
        if (isPrime(a[1])) cout << "Yes" << '\n';
        else cout << "No" << '\n';
        return 0;
    }
    if (n == 2) {
        if (sum >= 2 * n) {
            if (sum & 1) {
                if (isPrime(sum - 2)) cout << "Yes" << '\n';
                else cout << "No" << '\n';
            }
            else cout << "Yes" << '\n';
        }
        else cout << "No" << '\n';
        return 0;
    }

    if (sum >= 2 * n) {
        if (sum & 1) {
            if (isPrime(sum - (n - 1) * 2)) cout << "Yes" << '\n';
            else if (sum - (n - 3) * 2 - 3 >= 4) cout << "Yes" << '\n';
            else cout << "No" << '\n';
        }
        else cout << "Yes" << '\n';
    }
    else cout << "No" << '\n';
    return 0;
}

J

题解

知识点:拓扑排序。

显然,若原图是dag,拓扑序就是最终结果。

否则,一定需要两个排名序列,直接输出 \(1\)\(n\) 正序反序即可。

实现用一次拓扑排序即可完成判环和拓扑序。

时间复杂度 \(O(n+m)\)

空间复杂度 \(O(n+m)\)

代码

#include <bits/stdc++.h>
using namespace std;
using ll = long long;

const int N = 1e6 + 7;
int n, m;
vector<int> g[N];

int deg[N];
int ans[N], cnt;
queue<int> q;
void toposort() {
    cnt = 0;
    for (int i = 1;i <= n;i++) deg[i] = 0;
    for (int i = 1;i <= n;i++) for (auto v : g[i]) deg[v]++;
    for (int i = 1;i <= n;i++) if (!deg[i]) q.push(i);
    while (!q.empty()) {
        int u = q.front();
        q.pop();
        ans[++cnt] = u;
        for (auto v : g[u]) {
            deg[v]--;
            if (!deg[v]) q.push(v);
        }
    }
}

int main() {
    std::ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
    cin >> n >> m;
    for (int i = 1;i <= m;i++) {
        int u, v;
        cin >> u >> v;
        g[u].push_back(v);
    }

    toposort();

    if (cnt == n) {
        cout << 1 << '\n';
        for (int i = 1;i <= n;i++) cout << ans[i] << " \n"[i == n];
    }
    else {
        cout << 2 << '\n';
        for (int i = 1;i <= n;i++) cout << i << " \n"[i == n];
        for (int i = 1;i <= n;i++) cout << n + 1 - i << " \n"[i == n];
    }
    return 0;
}