2023 CCPC 秦皇岛

A. Make SYSU Great Again I

因为\(k \ge 2n\),所以可以顺序按照以阶梯形状摆放,这样可以保证每行每列两个,且\(\gcd\)都是 1,剩下的数字随便放就好了。

#include <bits/stdc++.h>

using namespace std;

using i32 = int32_t;
using i64 = long long;

#define int i64

using vi = vector<int>;
using pii = pair<int, int>;


i32 main() {
    ios::sync_with_stdio(false), cin.tie(nullptr);
    int n, k;
    cin >> n >> k;
    int t = 1;
    for (int i = 1; i <= n and t <= k; i++) {
        cout << i << " " << i << "\n", t++;
        if (i == n)
            cout << i << " " << 1 << "\n", t++;
        else
            cout << i << " " << i + 1 << "\n", t++;
    }
    for (int i = 1; i <= n and t <= k; i++) {
        for (int j = 1 + ( i == n); j < i and t <= k; j++) {
            cout << i << " " << j << "\n", t++;
        }
        for (int j = i + 2; j <= n and t <= k; j++) {
            cout << i << " " << j << "\n", t++;
        }
    }
    return 0;
}

D. Yet Another Coffee

考虑一种贪心的情况,把所有的打折卡按照结束时间排序,然后我们逐个打折卡考虑,我们把打折卡给最便宜的咖啡使用一定最优,因为打折卡是使得价格为负数。操作完后,贪心的选择最便宜的即可。

#include <bits/stdc++.h>

using namespace std;

using i32 = int32_t;
using i64 = long long;

#define int i64

using vi = vector<int>;
using pii = pair<int, int>;

void solve() {
    int n, m;
    cin >> n >> m;
    vi a(n);
    for (auto &i: a) cin >> i;
    vector<pii> b(m);
    for (auto &[r, w]: b) cin >> r >> w;
    ranges::sort(b);
    multiset<int> pre;
    int t = 0;
    for (int x; auto [r, w]: b) {
        while (t < n and t < r) pre.insert(a[t++]);
        x = *pre.begin(), pre.erase(pre.begin()), pre.insert(x - w);
    }
    while (t < n) pre.insert(a[t++]);
    for (int i = 1, res = 0; i <= n; i++) {
        res += *pre.begin(), pre.erase(pre.begin());
        cout << res << " ";
    }
    cout << "\n";
    return;
}

i32 main() {
    ios::sync_with_stdio(false), cin.tie(nullptr);
    int T;
    cin >> T;
    while (T--)
        solve();
    return 0;
}

F. Mystery of Prime

找规律发现对于两个奇偶相同的数\(x,y\),存在一个数\(k\)可以使得\(x+k,y+k\)均为质数。然后相邻的两个数奇偶必须不同,除了一种特殊情况,就是两个\(1\)相邻。

因此我们可以设计状态\(f[i][0/1/2/3]\)表示前\(i\)个数,且第\(i\)个数的状态为\(0\)不改变、\(1\)修改为\(1\)\(2\)修改为奇数、\(3\)修改为偶数。

然后转移就可以分为两类,一种相邻两个数,我们根据刚才说的奇偶不同和两个\(1\)的情况进行转移。还有一种是相邻三个数,两侧的数字就相同的情况。

#include<bits/stdc++.h>

using namespace std;

using vi = vector<int>;

const int inf = INT_MAX / 2;

bool is_prime(int x) {
    if (x < 2) return false;
    for (int i = 2; i * i <= x; i++)
        if (x % i == 0) return false;
    return true;
}

int main() {
    int n;
    cin >> n;
    vi a(n + 1);
    for (int i = 1; i <= n; i++) cin >> a[i];
    if (n == 2) {
        if (is_prime(a[1] + a[2])) cout << 0;
        else cout << 1;
        return 0;
    }

    vector f(n + 1, vi(4, inf));// 0 不变, 1 变1, 2 变奇, 3 变偶

    f[1][0] = 0;
    f[1][1] = (a[1] != 1);
    f[1][2] = f[1][3] = 1;

    for (int i = 2; i <= n; i++) {
        // 0
        if (is_prime(a[i] + a[i - 1])) f[i][0] = min(f[i][0], f[i - 1][0]);
        if (is_prime(a[i] + 1)) f[i][0] = min(f[i][0], f[i - 1][1]);
        if (a[i] % 2 == 1) f[i][0] = min(f[i][0], f[i - 1][3]);
        else f[i][0] = min(f[i][0], f[i - 1][2]);
        // 1
        if (is_prime(1 + a[i - 1])) f[i][1] = min(f[i][1], f[i - 1][0] + (a[i] != 1));
        f[i][1] = min(f[i][1], f[i - 1][1] + (a[i] != 1));
        f[i][1] = min(f[i][1], f[i - 1][3] + (a[i] != 1));
        // 2
        if (a[i - 1] % 2 == 0) f[i][2] = min(f[i][2], f[i - 1][0] + 1);
        f[i][2] = min(f[i][2], f[i - 1][3] + 1);
        // 3
        if (a[i - 1] % 2 == 1) f[i][3] = min(f[i][3], f[i - 1][0] + 1);
        f[i][3] = min(f[i][3], f[i - 1][1] + 1);
        f[i][3] = min(f[i][3], f[i - 1][2] + 1);
        // 改中间
        f[i][1] = min(f[i][1], f[i - 2][1] + 1 + (a[i] != 1)); // 1 x 1
        f[i][1] = min(f[i][1], f[i - 2][2] + 1 + (a[i] != 1)); // 奇 x 1
        f[i][2] = min(f[i][2], f[i - 2][2] + 2); // 奇 x 奇
        f[i][2] = min(f[i][2], f[i - 2][1] + 2); // 1 x 奇
        f[i][3] = min(f[i][3], f[i - 2][3] + 2); // 偶 x 偶
        if (a[i] % 2 == 1) { // ai 是 奇
            f[i][0] = min(f[i][0], f[i - 2][1] + 1); // 1 x ai
            f[i][0] = min(f[i][0], f[i - 2][2] + 1); // 奇 x ai
            if (a[i - 2] % 2 == 1) f[i][0] = min(f[i][0], f[i - 2][0] + 1); // ai-2 x ai
        } else { // ai 是 偶
            f[i][0] = min(f[i][0], f[i - 2][3] + 1);// 偶 x ai
            if (a[i - 2] % 2 == 0) f[i][0] = min(f[i][0], f[i - 2][0] + 1); // ai-2 x ai
        }
    }
    cout << ranges::min(f[n]);
    return 0;
}

G. Path

手推一下发现,其实答案就是\(\sum|a_i - a_{i-1}| + \sum |b_i - b_{i-1}|\)

#include <bits/stdc++.h>

using namespace std;

using i32 = int32_t;
using i64 = long long;

#define int i64

using vi = vector<int>;
using pii = pair<int, int>;


i32 main() {
    ios::sync_with_stdio(false), cin.tie(nullptr);
    int n, m;
    cin >> n >> m;
    vi a(n), b(m);
    for (auto &i: a) cin >> i;
    for (auto &i: b) cin >> i;
    int res = 0;
    for (int i = 1; i < n; i++)
        res += abs(a[i] - a[i - 1]);
    for (int i = 1; i < m; i++)
        res += abs(b[i] - b[i - 1]);
    cout << res;
    return 0;
}

J. Keyi LIkes Reading

其实只有13个物品,然后我们可以设计状态\(f[i][j]\)表示前\(i\)天选了\(j\)是否成立。其中\(j\)是二进制表示那些被选了。

因此我们可以用\(O(13\times (2^{13})^2)\)的代价转移出来。

#include <bits/stdc++.h>

using namespace std;

using i32 = int32_t;
using i64 = long long;

#define int i64

using vi = vector<int>;
using pii = pair<int, int>;


i32 main() {
    ios::sync_with_stdio(false), cin.tie(nullptr);
    vi cnt(13);
    int n, w;
    cin >> n >> w;

    for (int i = 1, x; i <= n; i++)
        cin >> x, cnt[x - 1]++;
    ranges::sort(cnt, greater<>());
    n = 0;
    while (n < 13 and cnt[n] != 0) n++;
    cnt.resize(n);
    vi can;
    int N = 1 << n;
    for (int i = 1, sum; i < N; i++) {
        sum = 0;
        for (int j = 0; j < n; j++)
            if ((i >> j) & 1) sum += cnt[j];
        if (sum <= w) can.push_back(i);
    }
    vi f(N);
    f[0] = 1;
    for (int i = 1; i <= n; i++) {
        auto g = f;
        for (int t = 1; t < N; t++) {
            for (auto p: can) {
                if ((t | p) != t) continue;
                g[t] |= f[t ^ p];
            }
        }
        f = move(g);
        if (f[N - 1]) {
            cout << i << "\n";
            return 0;
        }
    }
    return 0;
}

然后赛时因为写错了一点,导致越界进而导致 TLE。我按头写了一个优化,首先我们倒序枚举,这样可以优化空间,然后对于状态\(j\),我强制要求当前学习的单词中包含了\(j\)\(msg\),这样的话可以把复杂度减小\(\frac{1}{13}\)\(msg\)是最高有效位。

#include <bits/stdc++.h>

using namespace std;

using i32 = int32_t;
using i64 = long long;

#define int i64

using vi = vector<int>;
using pii = pair<int, int>;


i32 main() {
    ios::sync_with_stdio(false), cin.tie(nullptr);
    vi cnt(13);
    int n, w;
    cin >> n >> w;

    for (int i = 1, x; i <= n; i++)
        cin >> x, cnt[x - 1]++;
    ranges::sort(cnt, greater<>());
    n = 0;
    while (n <= 13 and cnt[n] != 0) n++;
    cnt.resize(n);

    auto msg = [&](int x) {
        for (int i = n - 1; i >= 0; i--) {
            if ((x >> i) & 1) return i;
        }
    };

    vector<vi> can(n);

    int N = 1 << n;
    for (int i = 1, sum; i < N; i++) {
        sum = 0;
        for (int j = 0; j < n; j++)
            if ((i >> j) & 1) sum += cnt[j];
        if (sum <= w) can[msg(i)].push_back(i);
    }
    vi f(N);
    f[0] = 1;
    for (int i = 1; i <= n; i++) {
        for (int t = N - 1; t >= 1; t--) {
            if (f[t]) continue;
            for (auto p: can[msg(t)]) {
                if ((t | p) != t) continue;
                if (f[t ^ p] == 0)continue;
                f[t] = 1;
                break;
            }
        }
        if (f[N - 1]) {
            cout << i << "\n";
            return 0;
        }
    }
    return 0;
}
posted @ 2024-09-16 22:45  PHarr  阅读(162)  评论(0编辑  收藏  举报