2024牛客暑期多校训练营2

2024牛客暑期多校训练营2

E-GCD VS XOR_2024牛客暑期多校训练营2 (nowcoder.com)

题意

给定 x,构造 y < x 使得 gcd(x, y) = x ⊕ y

思路

取 x − lowbit(x) 即可,如果 x 是 2 的整数次幂则无解。

代码

#include<bits/stdc++.h>

using namespace std;

using i64 = long long;

void solve() {

    i64 n;
    cin >> n;

    i64 ans = n - (n & -n);
    cout << (ans ? ans : -1) << '\n';

}

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

    int t;
    cin >> t;
    while (t--)
        solve();

    return 0;
}

C-Red Walking on Grid_2024牛客暑期多校训练营2 (nowcoder.com)

题意

给定一个 2 行 n 列的地图,有一些格子为障碍
任选起点,每个格子最多只能走一次(不能走到障碍),求最长路径

思路

考虑 dp,从左往右遍历,遇到上下都是 \(R\) 的时候说明上下可转移,在上面的由下和左转移,在下面的由上和左转移,然后更新答案。

代码

#include<bits/stdc++.h>

using namespace std;

using i64 = long long;

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

    int n;
    cin >> n;

    string s[2];
    cin >> s[0] >> s[1];

    array<int, 2> dp{0, 0};

    int ans = 0;
    for (int i = 0; i < n; i ++) {
        if (s[0][i] == 'R')
            dp[0] ++;
        else
            dp[0] = 0;
        if (s[1][i] == 'R')
            dp[1] ++;
        else
            dp[1] = 0;
        if (s[0][i] == 'R' && s[1][i] == 'R') {
            dp = {max(dp[0], dp[1] + 1), max({dp[1], dp[0] + 1})};
        }
        ans = max({ans, dp[0], dp[1]});
    }

    ans = max(ans - 1, 0);
    
    cout << ans << '\n';

    return 0;
}

H-Instructions Substring_2024牛客暑期多校训练营2 (nowcoder.com)

题意

平面直角坐标系中,小红初始站在原点
给定一个包含“上、下、左、右”的、长度为 n的指令序列,以及一个
特殊点 (x, y)
求选定一个子串,小红根据该子串序列移动,可以经过特殊点的方案数

思路

考虑前缀和记录每次到达的坐标,枚举左端点,对于从该左端点为起点第一次经过 (x,y) 的右端点,其右端点右边的点均为合法的点。

倒着枚举,找差分下标即可。

代码

#include<bits/stdc++.h>

using namespace std;

using i64 = long long;

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

    int n, x, y;
    cin >> n >> x >> y;

    string s;
    cin >> s;

    vector<array<int, 2>> a(n + 1);
    a[0] = {0, 0};

    for (int i = 0; i < n; i ++) {
        a[i + 1] = a[i];
        if (s[i] == 'W') {
            a[i + 1][1] ++;
        } else if (s[i] == 'S') {
            a[i + 1][1] --;
        } else if (s[i] == 'A') {
            a[i + 1][0]--;
        } else
            a[i + 1][0]++;
    }

    int ans = 0;
    map<array<int, 2>, int> idx;
    for (int i = n; i >= 0; i --) {
        idx[a[i]] = i;
        if (idx.count({a[i][0] + x, a[i][1] + y})) {
            int j = idx[ {a[i][0] + x, a[i][1] + y}];
            j = max(j, i + 1);
            ans += n - j + 1;
        }
    }

    cout << ans << '\n';

    return 0;
}

B-MST_2024牛客暑期多校训练营2 (nowcoder.com)

题意

给定一个 n 个点的简单带权无向图 G
每次询问一个点集 S,求 S 关于 G 导出子图的最小生成树
没有输出 −1

思路

考虑根号分治。

因为没有办法使用邻接矩阵,所有首先用 map,将整个图存下来
考虑两种可能的暴力

设以 \(\sqrt n\) 为界。

对于 \(k\le \sqrt n\):对于给定点集 S,双重循环枚举 S 中的每个点,把其中需要的
边均取出来,并排序,使用 kruskal 算法,求出最小生成树。

对于 \(k>\sqrt n\) :对于给定点集 S,循环枚举 S 中的每个点,在循环枚举该点的
所有边,将需要的边取出来,并排序,使用 kruskal 算法,求出最小生成树。

因为边是双向边,边数组的大小得开 2 倍。

代码

#include<bits/stdc++.h>

using namespace std;

using i64 = long long;

const int N = 1e5 + 10;
map<int, int> g[N];
vector<int> vis(N), node(N), fa(N);
vector<array<int, 3>> edge(N << 1);

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

    int n, m, q;
    cin >> n >> m >> q;

    for (int i = 0; i < m; i ++) {
        int u, v, w;
        cin >> u >> v >> w;
        g[u][v] = g[v][u] = w;
    }

    const int base = sqrt(n);

    auto find = [&](auto & self, int x)->int{
        return fa[x] == x ? x : fa[x] = self(self, fa[x]);
    };

    while (q--) {
        int k;
        cin >> k;

        for (int i = 0; i < k; i ++) {
            cin >> node[i];
            int u = node[i];
            fa[u] = u;
            vis[u] = 1;
        }

        int cnt = 0;
        if (k <= base) {
            for (int i = 0; i < k; i ++)
                for (int j = i + 1; j < k; j ++)
                    if (g[node[i]].count(node[j]))
                        edge[cnt++] = { g[node[i]][node[j]], node[i], node[j]};
        } else {
            for (int i = 0; i < k; i ++) {
                int u = node[i];
                for (auto &[v, w] : g[u])
                    if (vis[v])
                        edge[cnt++] = {w, u, v};
            }
        }

        sort(edge.begin(), edge.begin() + cnt);

        i64 ans = 0, kuai = 0;
        for (int i = 0; i < cnt; i ++) {
            auto &[w, u, v] = edge[i];
            u = find(find, u), v = find(find, v);
            if (u != v) {
                fa[u] = v;
                ans += w;
                kuai ++;
            }
            if (kuai == k - 1) break;
        }

        for (int i = 0; i < k; i ++) {
            int u = node[i];
            fa[u] = u;
            vis[u] = 0;
        }

        if (kuai != k - 1) ans = -1;

        cout << ans << '\n';

    }

    return 0;
}

I-Red Playing Cards_2024牛客暑期多校训练营2 (nowcoder.com)

题意

给定一个长度为 2 × n 的数组,1 到 n 每个元素恰好出现两次
每次操作可以删除一个长度不小于 2 的连续子数组,需要满足该子数组
首尾相同,获得该连续子数组“首尾元素值”乘以“元素数量”的分数
问最终可以得到的最大分数

思路

处理出 \(1\sim n\) 每个数包含的区间,考虑从大到小去枚举每个区间产生的贡献,因为大数产生的贡献一定比小数产生的更多。

\(f_x\) 表示 x 这个数所包含的区间所产生的贡献。

遍历 x 所包含的区间,设 \(dp_i\) 表示当前 \(l_x+1\sim i\) 所产生的贡献,如果对于 \(a_i\) 这个数,其 \([l_{a_i},r_{a_i}] \subset [l_x,r_x]\) ,那么有 \(dp_{r_{a_i}+1} = \max(dp_{r_{a_i}+1},dp_i+f_{a_i})\),对于其他位置, x 产生的贡献就是 x,即 \(dp_{i+1}=\max(dp_i+x,dp_{i+1})\),开始在两端补 0 的话,最后的答案即就是 \(f_0\)

代码

#include<bits/stdc++.h>

using namespace std;

using i64 = long long;

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

    int n;
    cin >> n;
    n ++;

    vector<int> a(2 * n);
    for (int i = 1; i < 2 * n - 1; i ++)
        cin >> a[i];

    vector<int> l(n, -1), r(n);
    for (int i = 0; i < 2 * n; i ++) {
        cin >> a[i];
        if (l[a[i]] == -1) {
            l[a[i]] = i;
        } else {
            r[a[i]] = i;
        }
    }

    vector<int> f(n);
    for (int x = n - 1; x >= 0; x --) {
        vector<int> dp(2 * n + 1);
        for (int i = l[x] + 1; i < r[x]; i ++) {
            if (dp[i + 1] < dp[i] + x)
                dp[i + 1] = dp[i] + x;
            if (i == l[a[i]] && r[a[i]] < r[x]) {
                if (dp[r[a[i]] + 1] < dp[i] + f[a[i]])
                    dp[r[a[i]] + 1] = dp[i] + f[a[i]];
            }
        }
        f[x] = 2 * x + dp[r[x]];
    }

    cout << f[0] << '\n';

    return 0;
}

G-The Set of Squares_2024牛客暑期多校训练营2 (nowcoder.com)

题意

定义一个多重数集合是好的,但且仅当集合中元素乘积是一个正整数的
平方,集合的权值为这个正整数的值。
求大小为 N的多重数集的所有子集中所有好集的权值和

思路

注意到 \(\sqrt{1000}\) 以内的质数只有 {2, 3, 5, 7, 11, 13, 17, 19, 23, 27, 31} 一共 11 个
注意到对于 1000 以内的数,质因子中不可能出现两个大于 32 的质数
按照大质数分组,小质数状态压缩当成背包的体积,做分组背包

小质数作为第一组,其他大质数各自一组,转移时对于第 11 位上的 1 要记得清零

注意:数字 1 根据写法不同,可能要特殊处理,大质数配对、小质数配
对都需要计算贡献,分类讨论不要遗漏情况

代码

#include<bits/stdc++.h>

using namespace std;

using i64 = long long;

constexpr i64 mod = 1e9 + 7, N = 1000;
i64 prime[12] = {2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31};

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

    int n;
    cin >> n;

    int m = 0;
    vector vec(N + 1, vector<array<int, 2>>());
    for (int i = 0; i < n; i ++) {
        int x;
        cin >> x;

        m = max(x, m);

        int mask = 0, val = 1;
        for (int j = 0; j < 11; j ++) {
            while (x % prime[j] == 0) {
                x /= prime[j];
                mask ^= 1 << j;
                if (!(mask >> j & 1)) {
                    val = val * prime[j] % mod;
                }
            }
        }
        vec[x].push_back({mask, val});
    }

    vector<i64> dp(1 << 12);
    dp[0] = 1;
    for (int i = 1; i <= m; i ++) {
        if (i * i <= 1000 && i != 1)
            continue;
        if (vec[i].empty())
            continue;

        prime[11] = i;
        for (auto [st, val] : vec[i]) {
            auto ndp = dp;

            if (i != 1)
                st |= 1 << 11;

            for (int j = 0; j < (1 << 12); j ++) {
                int both = j & st, nval = val;
                for (int k = 0; k < 12; k ++) {
                    if (both >> k & 1) {
                        nval = nval * prime[k] % mod;
                    }
                }
                ndp[j ^ st] += dp[j] * nval % mod;
                ndp[j ^ st] %= mod;
            }
            dp = move(ndp);
        }
        for (int j = 1 << 11; j < (1 << 12); j ++)
            dp[j] = 0;
    }

    cout << dp[0] - 1 << '\n';

    return 0;
}
posted @ 2024-07-22 21:26  Ke_scholar  阅读(34)  评论(0编辑  收藏  举报