Codeforces Round 931 (Div. 2) A-D2

A. Too Min Too Max

贪心、排序

对数组排序后,显然当下标 ijkl 分别选择 1n2n1 时求得最大值。

时间复杂度: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 个及以上时,可由价值更高的硬币替代;以此类推,价值为 3610 的硬币分别最多需要 142 个,然后剩余需要补齐的价值均由价值为 15 的硬币补齐。则通过分别枚举价值 13610 的硬币数量在所有可行解中取最小值为答案。

#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) ,依次询问可得到三条直线,三条直线上点的坐标均可由二元一次方程表示,解方程组并判断合法性即可求得交点。画图可知,交点的数量可能为 12 ,若为 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 数位为 1n 显然不为 1,则 n<ny

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

mn 的最高位 1 的数位上为 0 时,我们则考虑拆最高位;当 mn 的最高位 1 的数位上为 1 时,我们则考虑拆从高位往低位第一个出现的 n1m01

拆补完后,可以一步转换成 m,因为 n 在被拆的 1 的数位及其所有高位都与 m 相等,而其所有低位都已为 1,对每一位 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() {
    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

位运算、博弈论

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

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

k 为奇数时,如果是直接将所有为 1 的数位任意的分到两个数中,则一定有一个数的 k 为偶数,一个为奇数;如果用了 D1 中的拆分操作,则有一些数位由原本的 0 变为 11 变为 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 @   会有续命晴空  阅读(48)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】
点击右上角即可分享
微信分享提示