Loading

[题解] CF2109 Codeforces Round 1025 (Div. 2) (A~E)

A

对于1:不能全是1,因为 n - 1 场战斗最多有 n - 1 个玩家赢。

对于0:相邻两项不能都是0,因为战斗总是有人赢,有人输。

Code

\(O(n)\)

void solve() {
    int n;
    cin >> n;
    vector<int> a(n + 1);
    for (int i = 1; i <= n; i++) {
        cin >> a[i];
    }
    if (count(a.begin() + 1, a.end(), 1) == n) {
        cout << "YES\n";
        return;
    }

    for (int i = 2; i <= n; i++) {
        if (a[i] == 0 && a[i - 1] == 0) {
            cout << "YES\n";
            return;
        }
    }
    cout << "NO\n";
}

B

一个人负责调整棋子位置,一个人切棋盘。

切棋盘每次只能切掉一边,所以 横向的切纵向的切 是独立的。

调整棋子位置的人,把棋子放到中间最优,因为这样可以最小化 各种方向切的损失。(偶数个格子就没办法了,总有一边损失大 1 格)。

切棋盘的人,每次切掉的 行数(列数)越多越好

n 为行(列)的数量。n 为偶数时,能切掉 n / 2,还剩下 n / 2n 为奇数时,能切掉 n / 2,还剩下(n + 1) / 2。模拟这个切的过程。

对于怪物的初始位置,只能影响第一次切。因为结果可以在 \(\log\) 时间内计算出,我们各个方向都切一遍取最大值也是可以接受的。

详见代码。

Code

\(O(T*(\log n + \log m))\)

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

void solve() {
    i64 n, m, a, b;
    cin >> n >> m >> a >> b;
    auto calc = [&](i64 x, i64 y) {
        i64 res = 0;
        while (x > 1) {
            x = (x + 1) / 2;
            res++;
        }
        while (y > 1) {
            y = (y + 1) / 2;
            res++;
        }
        return res;
    };

    i64 ans = min({calc(n - a + 1, m), calc(a, m), calc(n, m - b + 1), calc(n, b)});
    cout << ans + 1 << '\n';
}

int main() {
    ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
    int tt = 1;
    cin >> tt;
    while (tt--) {
        solve();
    }
    return 0;
}

C1

我们只知道n,不知道x,只能把x做成一个确定的数,再通过这个数做出n。即,"add (n - 确定的数)"

基于此,我们可以观察到 digit操作可以很快的减小 x。因为x最大是1e9,第一次digit可以把x变成两位数,第二次digit可以把x变成 1~16之间的数。证明不能是17,可以考虑构造出17的两位数是89或者98。对于x <= 1e9,显然digit(x) <= 8118显然更不可能。

然后我们再用5次操作把x变成1就可以,-8, -4, -2, -1,最后add (n - 1)

稳定7次操作,可以通过。注意输入输出要求。

Code

void solve() {
    int n;
    cin >> n;
    int res;
    cout << "digit" << endl; // -> xx
    cin >> res;
    cout << "digit" << endl; // -> 1x
    cin >> res;
    cout << "add -8" << endl;
    cin >> res;
    cout << "add -4" << endl;
    cin >> res;
    cout << "add -2" << endl;
    cin >> res;
    cout << "add -1" << endl;
    cin >> res;
    cout << "add " << n - 1 << endl;
    cin >> res;
    cout << "!" << endl;
    cin >> res;
}

C2

次数要求严苛了,我们考虑其他的办法。

有个性质就是说,能被9整除的数,各位数字相加一定也能被9整除。这个性质很常见(3也有这个性质),我们可以基于此入手。

因为一次digit就能控制到两位数了,满足被9整除的两位数:18, 27, 36, 45, 54, 63, 72, 81, 90。一位数有个9。我们发现再次digit稳定变9

所以我们可以mul 9,然后 \(2\)digitx做成9,然后add (n - 9)

Code

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

void solve() {
    int n;
    cin >> n;
    int res;
    cout << "mul 9" << endl;
    cin >> res;
    cout << "digit" << endl; // -> xx
    cin >> res;
    cout << "digit" << endl; // -> 1x
    cin >> res;
    cout << "add " << n - 9 << endl;
    cin >> res;
    cout << "!" << endl;
    cin >> res;
}

int main() {
    ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
    int tt = 1;
    cin >> tt;
    while (tt--) {
        solve();
    }
    return 0;
}

C3

神人题。可以从题目中的1e9入手,或者从 C2 中的 9 的个数来入手。

手玩一下1 * 9999999992 * 999999999.....k * 999999999。可以发现每次都是把数从最低位往最高位挪。

但是每次digit都是相同的,81

直接利用这个性质可以在 \(3\) 次操作把x做成n,特判n == 81的情况只用 \(2\) 次。

Code

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

void solve() {
    int n;
    cin >> n;
    int res;
    cout << "mul 999999999" << endl;
    cin >> res;
    cout << "digit" << endl;
    cin >> res;
    if (n == 81) {
        cout << "!" << endl;
        cin >> res;
    } else {
        cout << "add " << n - 81 << endl;
        cin >> res;
        cout << "!" << endl;
        cin >> res;
    }
}

int main() {
    ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
    int tt = 1;
    cin >> tt;
    while (tt--) {
        solve();
    }
    return 0;
}

D

仔细读题,深入理解。

发现可以走回头路,所以我们可以在两个点间原地踏步,从而达到 奇偶性相同\(A_i\) ,大的可以当小的用。

我们发现答案只与 \(\sum A_i\) ,和 \(A_i\) 的奇偶性有关。

\(\sum A_i\) 是奇数,我们要找出偶数最长可达长度,\(\sum A_i\) 是偶数,我们要找出奇数最长可达长度。

我们发现我们只需要知道 \(\sum A_i\)\(\sum A_i - \min\{A_i| A_i为奇数\}\)

关键最短奇数长度路,和最短偶数长度路,我们可以建个双层图,BFS来得到。详情见代码。

Code

\(O(n+m+ℓ)\)

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

const int maxn = 4e5 + 5;
vector<int> g[maxn];

void solve() {
    int n, m, l;
    cin >> n >> m >> l;
    for (int i = 1; i <= 2 * n; i++) {
        g[i].clear();
    }
    i64 mi = (i64)1e18;
    vector<i64> a(l + 1);
    for (int i = 1; i <= l; i++) {
        cin >> a[i];
        if (a[i] % 2) {
            mi = min(mi, a[i]);
        }
    }
    for (int i = 1; i <= m; i++) {
        int u, v;
        cin >> u >> v;
        g[u].push_back(v + n);
        g[v + n].push_back(u);
        g[v].push_back(u + n);
        g[u + n].push_back(v);
    }

    vector<i64> d(2 * n + 1, (i64)1e18);
    vector<int> vis(2 * n + 1);
    d[1] = 0;
    queue<int> q;
    q.push(1);
    while (q.size()) {
        int x = q.front();
        q.pop();
        if (vis[x]) {
            continue;
        }
        vis[x] = 1;
        for (auto &v : g[x]) {
            if (d[v] > d[x] + 1) {
                d[v] = d[x] + 1;
                q.push(v);
            }
        }
    }

    vector<int> ans(n + 1);
    i64 sum = accumulate(a.begin() + 1, a.end(), 0LL);
    if (mi == (i64)1e18) {
        if (sum % 2) {
            for (int i = 1; i <= n; i++) {
                ans[i] |= d[i + n] <= sum;
            }
        } else {
            for (int i = 1; i <= n; i++) {
                ans[i] |= d[i] <= sum;
            }
        }
    } else {
        if (sum % 2) {
            for (int i = 1; i <= n; i++) {
                ans[i] |= d[i + n] <= sum;
                ans[i] |= d[i] <= sum - mi;
            }
        } else {
            for (int i = 1; i <= n; i++) {
                ans[i] |= d[i] <= sum;
                ans[i] |= d[i + n] <= sum - mi;
            }
        }
    }
    for (int i = 1; i <= n; i++) {
        cout << ans[i];
    }
    cout << '\n';
}

int main() {
    ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
    int tt = 1;
    cin >> tt;
    while (tt--) {
        solve();
    }
    return 0;
}

E

发现每个点i能不能被,只与i ~ n里拿了几次有关。

然后我们就可以倒着 dp。想出无后效性的 dp 数组形式。

Hint 2: 让 dp[i][j]表示在后缀 si,si+1,…,sn上恰好下出 j步的方法数。

显然 i 位的状态从 i + 1的状态转移。每一位可以被重复拿多次\((<=k)\),我们还要枚举这个多次。如若i位是0,那么他只能作为 i ~ n第 奇数次 的拿 插入 总拿数,这里还要用到组合数。

我们枚举 dp 数组的两维ij,再枚举第 i 位总共要拿l次, l <= j

具体细节写在代码注释了。

Code

\(O(nk^2)\)

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

const int mod = 998244353;
const int maxn = 5e2 + 2;

i64 f[maxn], g[maxn];
i64 ksm(i64 a, i64 n) {
    i64 ans = 1;
    a %= mod;
    while (n) {
        if (n & 1) {
            ans = ans * a % mod;
        }
        a = a * a % mod;
        n >>= 1;
    }
    return ans;
}

void init() {
    f[0] = 1;
    g[0] = 1;
    for (int i = 1; i < maxn; i++) {
        f[i] = f[i - 1] * i % mod;
        g[i] = g[i - 1] * ksm(i, mod - 2) % mod;
    }
}

inline i64 C(i64 n, i64 m) {
    if (m > n || n < 0 || m < 0) {
        return 0;
    }
    if (m == 0) {
        return 1;
    }
    return f[n] * g[m] % mod * g[n - m] % mod;
}

void solve() {
    int n, k;
    cin >> n >> k;
    vector<int> a(n + 1);
    for (int i = 1; i <= n; i++) {
        char c;
        cin >> c;
        a[i] = c - '0';
    }
    // Hint 2: 让 dp[i][j]表示在后缀 si,si+1,…,sn上恰好下出 j步的方法数。
    vector<vector<i64>> dp(n + 2, vector<i64>(k + 1));
    dp[n + 1][0] = 1;
    // 倒着看拿每 i 位
    for (int i = n; i >= 1; i--) {
        // i ~ n 位可以有 j 次拿
        for (int j = 0; j <= k; j++) {
            // 这一位可以有 l 次拿
            // 假设 | 为当前位拿,& 为 i + 1 ~ n 位拿的
            // 具象化 j == 9 的情况(奇数):
            // 当 a[i] == 0 的时候,最大的 l 情况是 |&|&|&|&|
            // 当 a[i] == 1 的时候,最大的 l 情况是 &|&|&|&|&
            // j == 8 的情况(偶数):
            // 当 a[i] == 0 的时候,最大的 l 情况是 |&|&|&|&
            // 当 a[i] == 1 的时候,最大的 l 情况是 &|&|&|&|
            // 用这个计算 l 的上限
            int u = j / 2 + (a[i] == 0 && j % 2);
            for (int l = 0; l <= u; l++) {
                // 求组合数的时候,| 只能插在 奇数位 或者 偶数位 (取决于 a[i]
                // 其实就是 l 的上限,如果不插 | 就自动插 &
                dp[i][j] = (dp[i][j] + dp[i + 1][j - l] * C(u, l)) % mod;
            }
        }
    }
    // 从 1 ~ n 里拿了 k 次
    cout << dp[1][k] << '\n';
}

int main() {
    init();
    ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
    int tt = 1;
    cin >> tt;
    while (tt--) {
        solve();
    }
    return 0;
}
posted @ 2025-05-18 15:42  Music163  阅读(337)  评论(0)    收藏  举报