Codeforces Round 929 (Div. 3) 题解(D-G)

比赛链接:https://codeforces.com/contest/1933

官解链接:https://codeforces.com/blog/entry/126560

质量不错的一场 D3。

以下所有问题解法都是 \(O(n)\)\(O(n \log n)\) 的(\(n\) 为问题规模),且比较明显,不再标注数据范围和时间复杂度。

CF1933D. Turtle Tenacity: Continual Mods

题意

将数组 \(a_{1..n} \ge 1\) 重排,问是否能使 \(((a_1 \mathbin {\rm mod} a_2) \mathbin {\rm mod} a_3) \cdots \mathbin {\rm mod} a_n \ne 0\)

题解

\(m\) 为原数组最小值 \(\min a\)。首先若最小值唯一,只要将其放在数组首位,它将始终不变,符合要求。另外,若有一个模 \(m\) 不为零的元素,将它和 \(m\) 放在数组的前两位,即可造出一个全新的唯一最小值,符合上面的条件。

除此之外总是无解的。证明:若所有元素都是 \(m\) 的倍数,则结果也总是 \(m\) 的倍数(因为 \((xm) \mathbin {\rm mod} (ym) = (x \mathbin {\rm mod} y ) m\))。而因为至少存在两个 \(m\),至少有一个 \(m\) 被用做过模数,在这一步答案一定变为 \(0\)

有另外一种判定方式,显然与上面是等价的:若 \(\gcd\) 的出现次数至少为两次,则说明答案为否。

代码实现

C++,gcd 判据
void solve() {
    int n;
    cin >> n;
    vector<int> a(n);
    for (int &x : a) cin >> x;
    int g = a[0];
    for (int x : a) g = gcd(g, x);
    cout << (ranges::count(a, g) <= 1 ? "YES" : "NO") << "\n";
}
Python,min 判据
for _ in range(int(input())):
    n = int(input())
    a = list(map(int, input().split()))
    m = min(a)
    if a.count(m) == 1 or any(x % m for x in a):
        print("YES")
    else:
        print("NO")

CF1933E. Turtle vs. Rabbit Race: Optimal Trainings

题意

给定数组 \(a_i\),每次查询给定 \(l\)\(u\),求满足条件的最小的 \(r\)

  • 最小化 \(\sum\limits_{i=1}^s (u + 1 - i)\),其中 \(s = \sum\limits_{i=l}^r a_i\)

题解

我们知道 \(\sum\limits_{i=1}^s (u + 1 - i)\) 是一个二次函数,且可以推出其对称轴为 \(u + \dfrac 1 2\),因此只需要找到离 \(u + \dfrac 1 2\) 最近的 \(s\)。这可以通过二分查找实现,找到对称轴左右两侧第一个位置,比较与对称轴的距离即可。注意边界。

代码实现

C++ 代码
void solve() {
    int n;
    cin >> n;
    vector<i64> a(n), pres(n + 1);
    for (int i = 0; i < n; i++) {
        cin >> a[i];
        pres[i + 1] = pres[i] + a[i];
    }
    int q;
    cin >> q;
    while (q--) {
        int l, u;
        cin >> l >> u;
        int r = ranges::upper_bound(pres, u + pres[l - 1]) - pres.begin();
        if (r == l) cout << r << " ";
        else if (r > n) cout << r - 1 << " ";
        else {
            if ((pres[r] - pres[l - 1]) + (pres[r - 1] - pres[l - 1]) <
                2 * u + 1) {
                cout << r << " ";
            } else {
                cout << r - 1 << " ";
            }
        }
    }
    cout << "\n";
}
Python 代码
from itertools import accumulate
from bisect import bisect_left

readInt = lambda: int(input())
for _ in range(readInt()):
    n = readInt()
    s = [0] + list(accumulate(map(int, input().split())))
    ans = []
    for _ in range(readInt()):
        l, u = map(int, input().split())
        r = bisect_left(s, s[l - 1] + u + 1)
        if r > n:
            ans.append(n)
        elif r == l:
            ans.append(l)
        else:
            if s[r] - s[l - 1] + s[r - 1] - s[l - 1] >= 2 * u + 1:
                ans.append(r - 1)
            else:
                ans.append(r)
    print(*ans)

CF1933F. Turtle Mission: Robot and the Earthquake

题意

一个 \(n \times m\) 的网格中,某些点存在石头。每一秒内,所有石头会同时向上移动一格,而主角可以选择向上、下、右移动一格。问是否能从 \((1, 1)\) 移动到 \((n, m)\) 点,并且不碰到任何石头。我们认为石头和主角的移动速度均为 \(1\),且在非整数时刻相遇也算碰撞。保证最后一列没有石头。所有上下的移动是循环的。

题解

我们切换到石头的坐标系,则相当于每一步停留在原地,向右下角移动一格,或向下移动两格。

下结论:在石头的坐标系走最短路移动至最后一列,之后再上下移动至终点,这个策略总是最优的。证明:在确定石头坐标系中到达最后一列时的位置时,晚到永远不会占便宜:由于最后一列没有石头,我们永远能花晚到的相同时间,走相同的跟随石头移动的路。

因此我们 BFS 求出石头坐标系中每个位置的最短路,并枚举最后一列更新答案即可。

C++ 代码
constexpr int INF = 1e9 + 7;
void solve() {
    int n, m;
    cin >> n >> m;
    vector<vector<int>> g(n, vector<int>(m));
    for (auto &v : g)
        for (auto &x : v) cin >> x;
    vector<vector<int>> dis(n, vector<int>(m, INF));
    queue<pair<int, int>> q;
    dis[0][0] = 0;
    q.push({0, 0});
    while (!q.empty()) {
        auto [i, j] = q.front();
        q.pop();
        if (j != m - 1 && !g[(i + 1) % n][j + 1]) {
            if (dis[(i + 1) % n][j + 1] == INF) {
                dis[(i + 1) % n][j + 1] = dis[i][j] + 1;
                q.push({(i + 1) % n, j + 1});
            }
        }
        if (!g[(i + 1) % n][j] && !g[(i + 2) % n][j]) {
            if (dis[(i + 2) % n][j] == INF) {
                dis[(i + 2) % n][j] = dis[i][j] + 1;
                q.push({(i + 2) % n, j});
            }
        }
    }
    int ans = INF;
    for (int i = 0; i < n; i++) {
        if (dis[i][m - 1] == INF) continue;
        int t = dis[i][m - 1];
        int accual_pos = ((i - t) % n + n) % n;
        // walk down later
        ans = min(ans, t + (n - 1 - accual_pos));
        // walk up later
        ans = min(ans, t + 1 + accual_pos);
    }
    if (ans == INF) cout << -1 << "\n";
    else cout << ans << "\n";
}
Python 代码
from collections import deque

INF = 10**9 + 7
for _ in range(int(input())):
    n, m = map(int, input().split())
    g = [list(map(int, input().split())) for _ in range(n)]
    dis = [[INF] * m for _ in range(n)]
    q = deque()
    q.append((0, 0))
    dis[0][0] = 0
    ans = INF
    while q:
        i, j = q.popleft()
        if j != m - 1:
            ni, nj = (i + 1) % n, j + 1
            if not g[ni][nj] and dis[ni][nj] == INF:
                dis[ni][nj] = dis[i][j] + 1
                q.append((ni, nj))
        else:
            pos = (i - dis[i][j]) % n
            ans = min(ans, dis[i][j] + min(pos + 1, n - 1 - pos))
        if not g[(i + 1) % n][j]:
            ni, nj = (i + 2) % n, j
            if not g[ni][nj] and dis[ni][nj] == INF:
                dis[ni][nj] = dis[i][j] + 1
                q.append((ni, nj))
    print(-1 if ans == INF else ans)

CF1933G. Turtle Magic: Royal Turtle Shell Pattern

题意

在一个 \(n \times m\)\(n, m \ge 5\))的平面上放两种棋子,求不存在三子连珠的方案数。

另外会增加多组限制,每个限制规定 \((i, j)\) 位置必须放某一种棋子。每次增加限制后,也输出满足所有限制和前面条件的方案数。

题解

结论:满足条件的棋子摆放本质上只有一种方式,是如下表摆放方式的无限循环:

1 1 0 0
0 0 1 1

符合条件的摆放方式即为它的平移和旋转,其中不同的总共有八种。

可以尝试使用类似搜索的方式,通过分类讨论证明这个结论。这里提供一种较为简短的思路。

首先证明一个 \(2 \times 2\) 的网格中不存在相同的三角:

1 1 0
1 1
0 0 ?

其中天蓝色的位置所放的棋子是之后推出的;而最终在红色位置导出矛盾。

那么 \(2 \times 2\) 网格中合法的方案本质上只有两种:

1 0
1 0

它已经能确定全平面。

1 0
0 1

在枚举 \((1,3)\) 处的棋子种类后,也能确定全平面。

我们已经得到了我们需要的 \(8\) 种排列,证毕。

回到问题本身,一开始输出 \(8\),之后每次查询对所有方案各自更新是否满足即可。这个判定可以打表,也有更简短的判定方式,参照代码。

代码实现

C++ 代码
int calc(int i, int j) { return (i % 2) ^ (j % 4 / 2); }

void solve() {
    int n, m, q;
    cin >> n >> m >> q;
    bitset<8> b = ~0;
    cout << 8 << "\n";
    while (q--) {
        int i, j;
        cin >> i >> j;
        string s;
        cin >> s;
        int c = s == "circle";
        for (int d : {0, 1, 2, 3}) {
            if (calc(i, j + d) ^ c) b[d] = 0;
            if (calc(j, i + d) ^ c) b[d + 4] = 0;
        }
        cout << b.count() << "\n";
    }
}
Python 代码
def calc(x, y):
    return (x + y // 2) & 1

for _ in range(int(input())):
    n, m, q = map(int, input().split())
    a = [1] * 8
    print(8)
    for _ in range(q):
        x, y, s = input().split()
        c = s[0] == "c"
        x, y = int(x), int(y)
        for d in range(4):
            a[d] &= calc(x, y + d) ^ c
            a[d + 4] &= calc(y, x + d) ^ c
        print(sum(a))
posted @ 2024-02-28 19:39  cccpchenpi  阅读(205)  评论(0编辑  收藏  举报