2024牛客暑期多校训练营1

A - A Bit Common

对于\(A\),最优解肯定是选择所有最低位为\(1\)的数。所以我枚举最低位为一的数的个数\(x\)

对于这个\(x\)个数,高位每一位的选择方法有\(2^x\)个,其中只有全\(1\)的情况与为\(1\),其他的\(x^x-1\)种都是\(0\),共有 \(m-1\)位,所以种类有\((2^x - 1)^{m-1}\)种。

对于剩下的\(n-x\)个数,高位任意取,所以\((2^x)^{m-1}\)

然后排列顺序有\(C_n^x\)种。

所以最终的答案就是

\[\sum C_n^x (2 ^ x) ^{m-1} (2^x - 1) ^{m-1} \]

#include<bits/stdc++.h>

using namespace std;


using i32 = int32_t;
using i64 = long long;

#define int i64

const int inf = INT_MAX / 2;

int mod;

int power(int x, int y) {
    int ans = 1;
    while (y) {
        if (y & 1) ans = ans * x % mod;
        x = x * x % mod, y /= 2;
    }
    return ans;
}


i32 main() {
    ios::sync_with_stdio(false), cin.tie(nullptr);
    int n, m;
    cin >> n >> m >> mod;
    int res = 0;
    map<int, int> cnt;
    for (int x = 1, p, q, y; x <= n; x++) {
        p = power(power(2, m - 1), n - x);
        q = power((power(2, x) - 1 + mod) % mod, m - 1);

        // 分子
        y = n - x + 1;
        for (int i = 2; i * i <= y; i++) {
            if (y % i != 0) continue;
            while (y % i == 0) cnt[i]++, y /= i;
        }
        if (y > 1) cnt[y]++;

        // 分母
        y = x;
        for (int i = 2; i * i <= y; i++) {
            if (y % i != 0) continue;
            while (y % i == 0) cnt[i]--, y /= i;
        }
        if (y > 1) cnt[y]--;

        y = 1;
        for (const auto &[pi, qi]: cnt)
            y = y * power(pi, qi) % mod;
        res = (res + p * q % mod * y % mod) % mod;
    }
    cout << res;

    return 0;
}

B - A Bit More Common

考虑这题与\(A\)题的不同。

题目要求的是至少两个子序列的与为\(1\)。正难则反,考虑求出只有一个子序列按位与为\(1\)的情况。然后用第一题答案减去只有一个的子序列的情况。

可以想到的是,只有一个序列的情况应当是选择所有最低位为 \(1\)的数字。那

依旧可以枚举最低位为\(1\)的数的个数为\(k\)。那么对于某一位来说,可以看作是一个\(k\)位的二进制数。

对于除最低外任意一位,如果有且仅有一个\(0\)的情况,那么这个\(0\)对应的数字应当是必须选择的。我们把这些位置称作“特殊位”。

如果对于\(k\)个数,每个数都至少有一个特殊位,哪么最终的情况一定只有一个就是这\(k\)个数全部都选择。

我们可以考虑枚举特殊位的个数\(t\)

首先对于特殊位来说,我们可以采用dp求解,记\(f[i][j]\)表示\(i\)个数字有\(j\)个特殊位的放置方法,则存在转移

\[f[i][j] = i * (f[i][j-1] + f[i-1][j-1]) \]

这个转移的考虑方法,可以参考第二类斯特林数。

对于非特殊位来说,总共的方案数有\(2^k\),其中全 1 的有\(1\)中,只有一个\(0\)的有\(k\)种,所以剩下可用的有\((2^k - k - 1)\)种,还剩下\(m - 1 - t\)位,所以方案为\((2^k - k - 1) ^{m-1}\)

还要考虑那些位是特殊位,共有\(C_{m - 1} ^{t}\)

因此如果有\(t\)个特殊位,则共有\(C_{m-1} ^ t \times f[k][t] \times (2 ^ k - k - 1 ) ^{m-1}\) 种。

考虑特殊的数量有\(t\in [k,m]\),所以所有只有一种的情况就是

\[\sum _{t = k } ^{m} C_{m-1} ^ t \times f[k][t] \times (2 ^ k - k - 1 ) ^{m-1} \]

用A题答案减去上述答案即可。

但是本题有些卡常,所以请尽量减少使用快速幂

#include<bits/stdc++.h>

using namespace std;


using i32 = int32_t;
using i64 = long long;

#define int i64

using vi = vector<int>;

const int inf = INT_MAX / 2;

int mod;

int power(int x, int y) {
    int ans = 1;
    while (y) {
        if (y & 1) ans = ans * x % mod;
        x = x * x % mod, y /= 2;
    }
    return ans;
}


const int N = 5005;
int C[N][N], S[N][N], pow2[N * N], powM[N];


i32 main() {
    ios::sync_with_stdio(false), cin.tie(nullptr);
    int n, m;
    cin >> n >> m >> mod;

    pow2[0] = 1;
    for (int i = 1, t = n * m; i <= t; i++)
        pow2[i] = pow2[i - 1] * 2 % mod;

    C[0][0] = 1;
    for (int i = 1, t = max(n, m); i <= t; i++) {
        C[i][0] = 1;
        for (int j = 1; j <= i; j++)
            C[i][j] = (C[i - 1][j] + C[i - 1][j - 1]) % mod;
    }
    S[0][0] = 1;
    for (int i = 1; i <= n; i++)
        for (int j = i; j <= m; j++)
            S[i][j] = i * (S[i][j - 1] + S[i - 1][j - 1]) % mod;

    int res = 0;

    for (int k = 2, p, q; k <= n; k++) {
        p = C[n][k] * pow2[(n - k) * (m - 1)] % mod;
        q = power((pow2[k] - 1 + mod) % mod, m - 1);
        powM[0] = 1;
        for (int i = 1; i <= m; i++)
            powM[i] = powM[i - 1] * (pow2[k] - k - 1 + mod) % mod;

        for (int t = k; t <= m; t++)
            q = (q - C[m - 1][t] * S[k][t] % mod * powM[m - 1 - t] % mod + mod) % mod;

        res = (res + p * q % mod) % mod;
    }
    cout << res % mod;
    return 0;
}

C - Sum of Suffix Sums

答案就是

\[\sum a_i \times i \]

#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>;

const int inf = INT_MAX / 2;

const int mod = 1e9 + 7;

i32 main() {
    ios::sync_with_stdio(false), cin.tie(nullptr);
    int q;
    cin >> q;
    vi a;
    int res = 0;
    for (int t, v; q; q--) {
        cin >> t >> v;
        while (t--) {
            res = (res - a.back() + mod) % mod;
            a.pop_back();
        }
        a.push_back((v * (a.size() + 1)) % mod);
        res = (res + a.back()) % mod;
        cout << res << "\n";
    }
    return 0;
}

D - XOR of Suffix Sums

考虑与上一题的不同,看起来可以用使用区间修改和区间求异或和。但实际上这样做很难维护且复杂度很高。

观察这个模数\(2097152 = 2 ^{21}\),因此这个相当于只用考虑\(21\)位即可。

考虑如果快速的维护后缀和。对于后缀和我门可以用前缀和来维护,也就是\(suf_i = pre _n - pre_{i-1}\)

我们考虑某一位,我们如果知道了\(pre_n - pre_i-1\) 中有多少个数字的这一位为\(1\)。就可以知道这一位在异或和中的贡献。

假设我们要求的是第\(d\)位,现在我们已经知道了\(pre_n\)中第\(d\)为是\(0\),只要求出有多少个数使得这一位变为\(1\)即可,如果是\(1\)就是多少个数使第\(d\)位不变即可。这些数如果我们只看前\(d+1\)位,就一定是一个连续的区间。因此可以使用值域树状数组求解。

#include <bits/stdc++.h>

using namespace std;

using vi = vector<int>;

struct BinaryIndexedTree {
    int n;
    vi b;

    BinaryIndexedTree() {}

    BinaryIndexedTree(int n) : n(n), b(n + 1) {};

    void resize(int newSize) {
        n = newSize;
        b = vi(n + 1);
        return;
    }

    int lowbit(int x) {
        return (x & -x);
    }

    void modify(int i, int y) {
        for (; i <= n; i += lowbit(i))
            b[i] += y;
        return;
    }

    int calc(int i) {
        int ans = 0;
        for (; i; i -= lowbit(i))
            ans += b[i];
        return ans;
    }

    int calc(int l, int r) {
        return calc(r) - calc(l - 1);
    }

};

const int M = 21, N = 5e6 + 10;

int main() {
    ios::sync_with_stdio(false), cin.tie(nullptr);

    int q;
    cin >> q;

    vector<BinaryIndexedTree> bit(M, BinaryIndexedTree(N));

    vi pre(1, 0);

    auto modify = [&bit](int x, int v) -> void {
        for (int i = 0, y, t = 2; i < M; i++, t <<= 1)
            y = x & (t - 1), bit[i].modify(y + 1, v);
        return;
    };

    auto query = [&bit](int x) -> int {
        int ans = 0;
        for (int i = 0, y, t = 2; i < M; i++, t <<= 1) {
            y = x & (t - 1);
            if ((y & (1 << i)) == 0) { // 0xxx的情况
                int l = y + 1, r = (1 << i) + y; // (0xxx, 1xxx]
                ans += (bit[i].calc(l + 1, r + 1) & 1) << i;
            } else { // 1xxx
                int l1 = 0, r1 = y - (1 << i); // [0, 0xxx]
                int l2 = y + 1, r2 = y + (1 << i); // (1xxx, 10xxx] -> (1xxx, 111] + [10000, 10xxx]
                ans += ((bit[i].calc(l1 + 1, r1 + 1) + bit[i].calc(l2 + 1, r2 + 1)) & 1) << i;
            }
        }
        return ans;
    };

    for (int t, v; q; q--) {
        cin >> t >> v;
        while (t--) {
            modify(pre[pre.size() - 2], -1);
            pre.pop_back();
        }
        pre.push_back(pre.back() + v);
        modify(pre[pre.size() - 2], 1);
        cout << query(pre.back()) << "\n";
    }
    return 0;
}

H - World Finals

考虑贪心,把能换的人都换走之后,在计算排名就好了

#include<bits/stdc++.h>

using namespace std;


using i32 = int32_t;
using i64 = long long;

#define int i64

const int inf = INT_MAX / 2;

struct node {
    string name;
    int p, t;

    node() {}

    node(string name, int p, int t) : name(name), p(p), t(t) {};

    bool operator<(node b) const {
        if (p != b.p) return p > b.p;
        return t < b.t;
    }
};

const string lzr = "lzr010506";

i32 main() {
    ios::sync_with_stdio(false), cin.tie(nullptr);

    int n;
    cin >> n;
    vector<node> a(n);
    set<string> nameA;
    for (auto &[name, p, t]: a)
        cin >> name >> p >> t, nameA.insert(name);

    int m;
    cin >> m;
    vector<node> b(m);
    set<string> nameB;
    for (auto &[name, p, t]: b)
        cin >> name >> p >> t, nameB.insert(name);

    int ans = 0;
    sort(a.begin(), a.end());
    for (auto &[name, p, t]: a) {
        ans++;
        if (name == lzr) break;
        if (nameB.count(name)) ans--;
    }

    int res = 0;
    sort(b.begin(), b.end());
    for (auto &[name, p, t]: b) {
        res++;
        if (name == lzr) break;
        if (nameA.count(name)) res--;
    }

    cout << min(ans, res);
    return 0;
}

I - Mirror Maze

考虑到光路可逆,因此所有的光路要么是一条链,要么是一个环。因此我们可以建图,对于每个镜子,我们建四个点,分别表示光线从四个方向射出去。然后对于链和环的情况,分别用搜索统计一下答案。最后\(O(1)\)回答就好了。

#include<bits/stdc++.h>

using namespace std;


using i32 = int32_t;
using i64 = long long;

using vi = vector<int>;
const vi dx = {-1, 1, 0, 0};
const vi dy = {0, 0, -1, 1};

// t 0...3 上下左右
i32 main() {
    ios::sync_with_stdio(false), cin.tie(nullptr);
    int n, m;
    cin >> n >> m;
    vector<string> g(n + 1);

    for (int i = 0; i < n; i++)
        cin >> g[i];

    auto f = [&](int x, int y, int t) -> int {
        return (x * m + y) * 4 + t;
    };
    int N = n * m * 4;
    vector<vi> e(N);
    vi deg(N);
    for (int x = 0; x < n; x++)
        for (int y = 0; y < m; y++)
            for (int t = 0, fx, fy, ft; t < 4; t++) {
                fx = x + dx[t], fy = y + dy[t];
                if (fx < 0 or fy < 0 or fx >= n or fy >= m) continue;
                if (g[fx][fy] == '-') {
                    if (t == 0) ft = 1;
                    if (t == 1) ft = 0;
                    if (t == 2) ft = 2;
                    if (t == 3) ft = 3;
                } else if (g[fx][fy] == '|') {
                    if (t == 0) ft = 0;
                    if (t == 1) ft = 1;
                    if (t == 2) ft = 3;
                    if (t == 3) ft = 2;
                } else if (g[fx][fy] == '\\') {
                    if (t == 0) ft = 2;
                    if (t == 1) ft = 3;
                    if (t == 2) ft = 0;
                    if (t == 3) ft = 1;
                } else {
                    if (t == 0) ft = 3;
                    if (t == 1) ft = 2;
                    if (t == 2) ft = 1;
                    if (t == 3) ft = 0;
                }
                e[f(x, y, t)].push_back(f(fx, fy, ft));
                deg[f(fx, fy, ft)] = 1;
            }

    vi res(N), vis(N);
    set<int> cnt;
    auto dfs = [&](auto &&dfs, int x) -> void {
        vis[x] = 1;
        if (e[x].empty()) return;
        int y = e[x].front();
        dfs(dfs, y);
        if (x % 4 != y % 4) cnt.insert(y / 4);
        res[x] = cnt.size();
        return;
    };

    for (int i = 0; i < N; i++) {
        if (deg[i]) continue;
        cnt.clear();
        dfs(dfs, i);
    }


    for (int i = 0; i < N; i++) {
        if (vis[i]) continue;
        queue<int> q;
        q.push(i);
        cnt.clear();
        while (not q.empty()) {
            int x = q.front();
            q.pop();
            vis[x] = 1;
            if (e[x].empty()) continue;
            int y = e[x].front();
            if (x % 4 != y % 4) cnt.insert(y / 4);
            if (vis[y]) continue;
            res[y] = -i, q.push(y);
        }
        res[i] = cnt.size();
    }
    int q;
    cin >> q;
    string dir;
    for (int x, y, t, r; q; q--) {
        cin >> x >> y >> dir, x--, y--;
        if (dir == "above") t = 0;
        if (dir == "below") t = 1;
        if (dir == "left") t = 2;
        if (dir == "right") t = 3;
        r = f(x, y, t);
        if (res[r] < 0) cout << res[-res[r]] << "\n";
        else cout << res[r] << "\n";
    }
    return 0;
}
posted @ 2024-07-20 14:46  PHarr  阅读(84)  评论(0编辑  收藏  举报