Codeforces Round 931 (Div. 2) A-D2

A. Too Min Too Max

贪心、排序

对数组排序后,显然当下标 \(i\)\(j\)\(k\)\(l\) 分别选择 \(1\)\(n\)\(2\)\(n - 1\) 时求得最大值。

时间复杂度:\(O(nlogn)\)

#include <bits/stdc++.h>

using namespace std;

#define cctie ios::sync_with_stdio(0);cin.tie(0);cout.tie(0)
#define all(x) x.begin(), x.end()
#define ALL(x) x.begin() + 1, x.end()

using i64 = long long;

void solve() {
    int n;
    cin >> n;
    
    vector<int> a(n + 1);
    for (int i = 1; i <= n; i++) {
        cin >> a[i];
    }

    sort(ALL(a));

    cout << 2 * (a[n] + a[n - 1] - a[1] - a[2]) << '\n';
}

void prework() {

}

int main() {
    cctie;

    prework();

    int T;
    cin >> T;

    while (T--) {
        solve();
    }

    return 0;
} 

B. Yet Another Coin Problem

枚举、贪心

价值为 \(1\) 的硬币最多需要 \(2\) 个,当需要 \(3\) 个及以上时,可由价值更高的硬币替代;以此类推,价值为 \(3\)\(6\)\(10\) 的硬币分别最多需要 \(1\)\(4\)\(2\) 个,然后剩余需要补齐的价值均由价值为 \(15\) 的硬币补齐。则通过分别枚举价值 \(1\)\(3\)\(6\)\(10\) 的硬币数量在所有可行解中取最小值为答案。

#include <bits/stdc++.h>

using namespace std;

#define cctie ios::sync_with_stdio(0);cin.tie(0);cout.tie(0)
#define all(x) x.begin(), x.end()
#define ALL(x) x.begin() + 1, x.end()

using i64 = long long;

void solve() {
    int n;
    cin >> n;

    int ans = n;
    for (int i = 0; i < 3; i++) {
        for (int j = 0; j < 2; j++) {
            for (int k = 0; k < 5; k++) {
                for (int l = 0; l < 3; l++) {
                    int x = n - i - j * 3 - k * 6 - l * 10;
                    if (x >= 0 && x % 15 == 0) {
                        ans = min(ans, i + j + k + l + x / 15);
                    }
                }
            }
        }
    }
    cout << ans << '\n';
}
 
void prework() {

}

int main() {
    cctie;

    prework();

    int T;
    cin >> T;

    while (T--) {
        solve();
    }

    return 0;
} 

C. Find a Mine

数学

随机选择一个点作为询问点时,距离询问点最近的雷可能存在的位置太多,不好在 \(4\) 次询问中确定一个雷的具体位置。而通过第一个样例的提示,我们发现当询问点为两条边界的交点时,可以得到距离最近的地雷可能的位置会在一条直线上,则我们的策略为选择三个这样的点(因为会受另一个地雷的干扰,仅选两个点是不够的),不妨选择 \((1, 1)\)\((1, m)\)\((n, 1)\) ,依次询问可得到三条直线,三条直线上点的坐标均可由二元一次方程表示,解方程组并判断合法性即可求得交点。画图可知,交点的数量可能为 \(1\)\(2\) ,若为 \(1\) 时,显然交点即为所求点;若为 \(2\) 时,则第 \(4\) 次询问用于筛除其中一点。

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

#include <bits/stdc++.h>

using namespace std;

#define cctie ios::sync_with_stdio(0);cin.tie(0);cout.tie(0)
#define all(x) x.begin(), x.end()
#define ALL(x) x.begin() + 1, x.end()

using i64 = long long;

int ask(int x, int y) {
    cout << "? " << x << " " << y << endl;
    int d;
    cin >> d;
    return d;
}

void get(int x, int y) {
    cout << "! " << x << " " << y << endl;
}

void solve() {
    int n, m;
    cin >> n >> m;

    int d1 = ask(1, 1), d2 = ask(1, m), d3 = ask(n, 1);
    int x1 = (d1 + d2 + 3 - m) / 2, y1 = x1 - (d2 - m + 1);
    int y2 = (d1 + d3 + 3 - n) / 2, x2 = y2 - (d3 - n + 1);
    
    auto is_legal = [&](int x, int y)->bool {
        return 1 <= x && x <= n && 1 <= y && y <= m && x + y == 2 + d1;
    };

    if (is_legal(x1, y1)) {
        if (is_legal(x2, y2)) {
            int d = ask(x1, y1);
            if (d) {
                get(x2, y2);
            } else {
                get(x1, y1);
            }
        } else {
            get(x1, y1);
        }
    } else {
        get(x2, y2);
    }
}
 
void prework() {

}

int main() {
    cctie;

    prework();

    int T;
    cin >> T;

    while (T--) {
        solve();
    }

    return 0;
} 

D1. XOR Break — Solo Version

位运算、分类讨论

这里讨论一种最多只需要操作 2 次的做法。

当二进制数 \(n\) 包含 \(1\) 的个数为 \(1\) 时,显然对 \(n\) 已无法进行任何操作,因为选择任意一个比 \(n\) 小的数 \(y\) 来异或时,\(y\) 数位为 \(1\)\(n\) 显然不为 \(1\),则 \(n \lt n \oplus y\)

当二进制数 \(n\) 包含多个 \(1\) 时,我们可以进行一种核心操作拆 \(1\)\(1\) 的操作,比如 \(n = 101001\),我们可以选择 \(y = 001110\)\(n\) 异或,得 \(100111\) ,即拆了某个非最高位的 \(1\) 然后使该位之后的所有数位都为 \(1\) ,这是一种拆非最高位的操作,当然也可以拆最高位,刚刚的例子里,\(n\) 不变,我们可以选择 \(y = 100110\)\(n\) 异或,得 \(001111\) ,即使得次高位及其之后的所有位为 \(1\) 。通过刚才的例子我们发现,我们没法在最高位和次高位的两个 \(1\) 之间进行任何拆补操作,所以当在这些位中 \(m\)\(1\)\(n\)\(0\) 时,则无法实现转换,否则我们可以通过拆补来实现 \(n\)\(m\) 的转换。

\(m\)\(n\) 的最高位 \(1\) 的数位上为 \(0\) 时,我们则考虑拆最高位;当 \(m\)\(n\) 的最高位 \(1\) 的数位上为 \(1\) 时,我们则考虑拆从高位往低位第一个出现的 \(n\)\(1\)\(m\)\(0\)\(1\)

拆补完后,可以一步转换成 \(m\),因为 \(n\) 在被拆的 \(1\) 的数位及其所有高位都与 \(m\) 相等,而其所有低位都已为 \(1\),对每一位 \(1\)\(0\) 的转换是可以直接进行的(通过异或)。

#include <bits/stdc++.h>

using namespace std;

#define cctie ios::sync_with_stdio(0);cin.tie(0);cout.tie(0)
#define all(x) x.begin(), x.end()
#define ALL(x) x.begin() + 1, x.end()

using i64 = long long;

void solve() {
    i64 n, m;
    cin >> n >> m;

    if (__builtin_popcountll(n) == 1) {
        cout << -1 << '\n';
    } else {
        int h = 0, k = 0;
        for (int i = 62; ~i; i--) {
            int u = n >> i & 1, v = m >> i & 1;
            if (u) {
                if (!h) {
                    h = i;
                }
                if (!v) {
                    k = i;
                    break;
                }
            }
        }

        vector<i64> ans(1, n);
        n -= 1LL << k;
        if (h == k) {
            int lh;
            for (int i = h - 1; ~i; i--) {
                int u = n >> i & 1, v = m >> i & 1;
                if (v > u) {
                    cout << -1 << '\n';
                    return;
                }
                if (u) {
                    lh = i;
                    break;
                }
            }
            k = lh;
        }
        for (int i = k - 1; ~i; i--) {
            int u = n >> i & 1;
            if (!u) {
                n += 1LL << i;
            }
        }
        ans.push_back(n);
        if (n != m) {
            ans.push_back(m);
        }

        cout << ans.size() - 1 << '\n';
        for (i64 i : ans) {
            cout << i << " \n"[i == m];
        }
    }
}

void prework() {

}

int main() {
    cctie;

    prework();

    int T;
    cin >> T;

    while (T--) {
        solve();
    }

    return 0;
} 

D2. XOR Break — Game Version

位运算、博弈论

我们先分析 \(n\)\(1\) 的个数 \(k\) 较少的情况:\(k = 1\) 时,无法操作,先手必败;\(k = 2\) 时,先手可以拆成两个 \(k = 1\) 的数,先手必胜,不难想到 先手胜负 与 \(k\) 的奇偶有关。

\(k\) 为偶数时,一定可以分解成两个 \(k\) 为奇数的数;

\(k\) 为奇数时,如果是直接将所有为 \(1\) 的数位任意的分到两个数中,则一定有一个数的 \(k\) 为偶数,一个为奇数;如果用了 \(D1\) 中的拆分操作,则有一些数位由原本的 \(0\) 变为 \(1\)\(1\) 变为 \(0\),当有一个 \(0\) 变为 \(1\) 时,分解后的两个数的 \(k\) 的奇偶性同时改变,\(1\) 变为 \(0\) 也是如此,而分解后的两个数的 \(k\) 奇偶性总是互异的,所以得出分解后的两个数的 \(k\) 一奇一偶。

所以我们得到偶必得奇且奇能得偶,而 \(k = 1\) 时无法操作且游戏为有限轮( \(D1\) 中的拆分操作是有限次的),得出 \(k\) 为偶数先手必胜, \(k\) 为奇数先手必败的结论。

由于操作次数限制在不超过 \(63\) 次,为了避免拆分操作不断添加 \(1\) 的数量而延长操作次数,每到 \(Alice\) 操作时,我们就选择将 \(n\) 分解成 \(n\) 的最高位和 \(n\) 的剩余部分,这样可以保证每次能将 \(1\) 的上限数量减一,使操作次数在限制内。

#include <bits/stdc++.h>

using namespace std;

#define cctie ios::sync_with_stdio(0);cin.tie(0);cout.tie(0)
#define all(x) x.begin(), x.end()
#define ALL(x) x.begin() + 1, x.end()

using i64 = long long;

void solve() {
    i64 n;
    cin >> n;

    int turn;
    if (__builtin_popcountll(n) & 1) {
        cout << "second" << endl;
        turn = 1;
    } else {
        cout << "first" << endl;
        turn = 0;
    }

    i64 p1 = 1, p2 = 1;
    while (p1 != 0 || p2 != 0) {
        if (!turn) {
            for (int i = 62; ~i; i--) {
                int u = n >> i & 1;
                if (u) {
                    p1 = 1LL << i;
                    break;
                }
            }
            p2 = n ^ p1;

            cout << p1 << " " << p2 << endl;
        } else {
            cin >> p1 >> p2;

            if (__builtin_popcountll(p1) & 1) {
                n = p2;
            } else {
                n = p1;
            }
        }
        turn ^= 1;
    }
}

void prework() {

}

int main() {
    cctie;

    prework();

    int T;
    cin >> T;

    while (T--) {
        solve();
    }

    return 0;
} 
posted @ 2024-03-02 22:18  会有续命晴空  阅读(41)  评论(0编辑  收藏  举报