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