Mobile wallpaper 1Mobile wallpaper 2Mobile wallpaper 3Mobile wallpaper 4Mobile wallpaper 5
2870 字
14 分钟
2025牛客暑期多校训练营5
2025-07-30
统计加载中...

大概情况是这样的

STATUSCOUNT
AC3
赛后补4

做了 3 个补了四个,显然是赛场上出了点问题,做完最简单的三个之后我们被榜误导了,H 题很简单但是我们没看,先看了比较难的 K 题,K 题思路出来之后最终 40min 没有战胜,然后三道题遗憾离场了,排名是 373.

A. Entangled Coins #

这道题属实感觉有点不应该,我中间推对了很多结论,但是后面不知道怎么又给自己推翻了。

我思维卡在的点是

  • 操作奇数次和偶数次得到的式子不一样,没有想到把奇数拆成一个 1 和一个偶数分析;
  • 只操作偶数次的结论其实已经大致得出了,但是边界没有处理好,晕进去了,没有想到 n < 2k 会卡边界的情况下用互相抵消的思想把 k 转化成 n - k 优化掉恶心的边界情况。

应该继续往下想的,我看完题第一句就说这题的结论应该不复杂……实际上也真不复杂,但是赛场上还是没想到

#include <iostream>
using namespace std;
int main() {
int T;
scanf("%d", &T);
while (T--) {
long long n, k, s, t;
scanf("%lld%lld%lld%lld", &n, &k, &s, &t);
if (s == t) printf("0\n");
else if (n == k) {
if (s + t == n) printf("1\n");
else printf("-1\n");
}
else if ((t - s & 1) && (k % 2 == 0)) printf("-1\n");
else if (t - s & 1) {
int l = abs(s - k), r = min(s + k, 2 * n - s - k);
if (l <= t && t <= r) {
printf("1\n");
continue;
}
int d = min(abs(l - t), abs(r - t));
k = min(k, n - k);
printf("%lld\n", 1 + 2 * ((d + k * 2 - 1) / (k * 2)));
}
else if (k & 1) {
int d = abs(s - t);
k = min(k, n - k);
printf("%lld\n", 2 * ((d + k * 2 - 1) / (k * 2)));
}
else {
int l = abs(s - k), r = min(s + k, 2 * n - s - k);
if (l <= t && t <= r) {
printf("1\n");
continue;
}
int d1 = min(abs(l - t), abs(r - t)), d2 = abs(s - t);
k = min(k, n - k);
printf("%lld\n", min(1 + 2 * ((d1 + k * 2 - 1) / (k * 2)), 2 * ((d2 + k * 2 - 1) / (k * 2))));
}
}
return 0;
}

C. Array Deletion Game #

我是对博弈论先天性抗拒,但是这道题的性质还真不难,问题也是我们没有人看这道题。逆向思维很容易想到,比较难注意到的性质是,因为左右各差 1 个位置,l + r 相同的要么全必胜要么全必负,二分卡到中间最小的那个合法区间,现在左右端点有一个就不能动了,把移动左端点和移动右端点的情况分别讨论一下取并。这个转化也很巧妙。

#include <iostream>
using namespace std;
const int N = 100010;
int a[N];
int main() {
int n, q;
scanf("%d", &n);
for (int i = 1; i <= n; ++i) {
scanf("%d", &a[i]);
a[i] += a[i - 1];
}
scanf("%d", &q);
while (q--) {
int s;
scanf("%d", &s);
int l = 0, r = n / 2;
while (l < r) {
int mid = l + r + 1 >> 1;
if (a[n - mid] - a[mid] > s) l = mid;
else r = mid - 1;
}
int d = l;
// cout << d << ' ';
l = 1 + d, r = n - d + 1;
while (l < r) {
int mid = l + r >> 1;
if (a[n - d] - a[mid - 1] <= s) r = mid;
else l = mid + 1;
}
// cout << l << ' ' ;
if (l - d & 1) {
// cout << endl;
printf("Alice\n");
continue;
}
l = d, r = n - d;
while (l < r) {
int mid = l + r + 1 >> 1;
if (a[mid] - a[d] <= s) l = mid;
else r = mid - 1;
}
// cout << l << endl;
if (n - d - l + 1 & 1) {
printf("Alice\n");
}
else printf("Bob\n");
}
return 0;
}

E. Mysterious XOR Operation#

这道题是我想的 + 我做的,想到的有点慢了,如果没有限制条件,传统的做法就是对每个二进制位分别前缀和,对于每个数枚举数位和前缀和比较,统计答案。加上第奇数个 1 的限制之后统计就稍微复杂了,本来我想着要 dp,但是是不合理的,如果做了线性 dp 相当于把所有数位拆开排列组合了一遍,显然不对;应该做的是统计。后来灵机一动想到无论如何两个后缀 1 个数都是偶数的数的后缀异或之后 1 的个数一定还是偶数,因为只能两个 1 相消偶数 + 偶数 - 偶数还是偶数,于是就解决了。

#include <iostream>
#include <cstring>
using namespace std;
int cnt[40][2][2];
int main() {
int n;
scanf("%d", &n);
long long res = 0;
for (int i = 0; i < n; ++i) {
int t;
scanf("%d", &t);
for (int j = 0, k = 0; j < 32; ++j) {
int c = t >> j & 1;
res += (long long)cnt[j][c ^ 1][k] << j;
k ^= t >> j & 1;
}
for (int j = 0, k = 0; j < 32; ++j) {
cnt[j][t >> j & 1][k]++;
k ^= t >> j & 1;
}
}
printf("%lld\n", res);
return 0;
}

H. VI Civilization #

这道是最可惜的一道,似乎可能是题干比较长[?]没人做,实际上很简单,我们都被骗了,一眼都没看,其实就是一个简单的二分和一个简单的 dp。

虽然但是,如果我看到了去敲代码了可能会弄出来一大堆 Floating Point Exception. 后面补题的时候没特判 mid == 0 的情况出了好几次问题。

#include <iostream>
#include <cstring>
using namespace std;
const int N = 110;
int a[N], k[N], b[N], c[N]; // 需要的科技点 科技点增量 尤里卡需要的生产力 尤里卡减少的科技点
int f[N][N][N]; // 前 i 轮使用了 j 次生产力完成了 k 个科技
int m, s, t, n; // 科技点 胜利需要的科技点 回合限制 总个数
bool check(int mid) {
int res = 0;
for (int i = 1; i <= t; ++i) {
for (int j = 0; j <= i; ++j) {
for (int l = 0; l <= n; ++l) {
f[i][j][l] = -0x3f3f3f3f;
f[i][j][l] = max(f[i][j][l], f[i - 1][j][l] + m + k[l]);
if (i - (a[l] + m + k[l - 1] - 1) / (m + k[l - 1]) >= 0)
f[i][j][l] = max(f[i][j][l], f[i - (a[l] + m + k[l - 1] - 1) / (m + k[l - 1])][j][l - 1]);
if (mid != 0 && i - (a[l] - c[l] + m + k[l - 1] - 1) / (m + k[l - 1]) >= 0 && j - (b[l] + mid - 1) / mid >= 0)
f[i][j][l] = max(f[i][j][l], f[i - (a[l] - c[l] + m + k[l - 1] - 1) / (m + k[l - 1])][j - (b[l] + mid - 1) / mid][l - 1]);
if (f[i][j][l] >= s) return true;
}
}
}
return false;
}
int main() {
scanf("%d%d%d%d", &m, &s, &t, &n);
for (int i = 1; i <= n; ++i) {
scanf("%d%d%d%d", &a[i], &k[i], &b[i], &c[i]);
k[i] += k[i - 1];
}
memset(f, -0x3f, sizeof(f));
f[0][0][0] = 0;
int l = 0, r = 1000000001;
while (l < r) {
int mid = l + r >> 1;
if (check(mid)) r = mid;
else l = mid + 1;
}
printf("%d\n", l == 1000000001 ? -1 : l);
return 0;
}

I. Block Combination Minimal Perimeter#

超级大水题。

#include <iostream>
using namespace std;
int main() {
int n;
cin >> n;
if (n & 1) cout << 2 * ((n + 1) / 2 + n) << endl;
else cout << 2*((n / 2) + n + 1) << endl;
return 0;
}

J. Fastest Coverage Problem 队友#

这也是一道简单题,逻辑上来说二分答案就可以,实际上当时写代码的时候并不顺利,WA 了两次才过。

后面我补题的时候也 WA 了好几次,而且 97.6%.

  • 没有处理一个 1 都没有的清况;
  • 处于最角上四个不合法块夹出来的矩形的中心正好对着格的间隙……这个情况我感觉不太好处理,于是干脆多遍历了一遍,枚举中心点,验证四个角,常数 * 2,变相解决了这个边界问题。
#include <iostream>
#include <queue>
#include <vector>
#include <climits>
using namespace std;
vector<vector<int>> mp, f;
int n, m;
bool check(int mid) {
int mnxpy = INT_MAX, mnxmy = INT_MAX, mxxpy = INT_MIN, mxxmy = INT_MIN;
for (int i = 0; i < n; ++i) {
for (int j = 0; j < m; ++j) {
// cout << f[i][j] << ' ';
if (f[i][j] > mid) {
mnxpy = min(mnxpy, i + j);
mnxmy = min(mnxmy, i - j);
mxxpy = max(mxxpy, i + j);
mxxmy = max(mxxmy, i - j);
}
}
// cout << endl;
}
if (mxxpy == INT_MIN) return true;
else {
// cout << mxxpy << ' ' << mnxpy << ' ' << mxxmy << ' ' << mnxmy << endl;
for (int i = 0; i < n; ++i) {
for (int j = 0; j < m; ++j) {
if (max(max(mxxpy - i - j, i + j - mnxpy), max(mxxmy - i + j, i - j - mnxmy)) <= mid) {
// cout << i << ' ' << j << ' ' << max(max(mxxpy - i - j, i + j - mnxpy), max(mxxmy - i + j, i - j - mxxmy)) << endl;
return true;
}
}
}
return false;
}
}
int main() {
scanf("%d%d", &n, &m);
mp = vector<vector<int>>(n, vector<int>(m));
f = vector<vector<int>>(n, vector<int>(m, n * m));
queue<pair<int, int>> q;
for (int i = 0; i < n; ++i) {
for (int j = 0; j < m; ++j) {
scanf("%d", &mp[i][j]);
if (mp[i][j]) {
f[i][j] = 0;
q.push({i, j});
}
}
}
int dx[] = {0, 1, 0, -1}, dy[] = {1, 0, -1, 0};
while (!q.empty()) {
auto s = q.front();
q.pop();
for (int i = 0; i < 4; ++i) {
auto t = s;
t.first += dx[i], t.second += dy[i];
if (t.first >= 0 && t.first < n && t.second >= 0 && t.second < m && f[t.first][t.second] == n * m) {
f[t.first][t.second] = f[s.first][s.second] + 1;
q.push(t);
}
}
}
int l = 0, r = n * m;
while (l < r) {
int mid = l + r >> 1;
if (check(mid)) r = mid;
else l = mid + 1;
}
printf("%d\n", l);
return 0;
}

K. Perfect Journey #

佬们放着简单的 H 不做,都来 K 题当卡常高手了……

树上路径只要两个端点确定了那就是一定的,只考虑关键边下到的节点构成的虚树的话顶多只有 m2 种路径,只能有 m2 种合法的 bitmask,然后做状压 dp,如果常数优秀的话可以恰好过了。

离谱的问题:

最后的状压 dp 里有这么一行代码

way[j | mask[i]] = ((long long)way[j | mask[i]] + (long long)way[j] * cnt[i] % MOD) % MOD;
  • 最快的情况,第一个 long long 不加,同时后面的乘法取模
  • 慢 600 ms 的情况,第一个 long long 加上,同时后面的乘法取模
  • 慢 400 ms 的情况,第一个 long long 不加,同时后面不取模

那个佬可以解释一下为什么😭

#include <iostream>
#include <cstring>
#include <bitset>
using namespace std;
const int N = 200010;
const int MOD = 998244353;
int head[N], ver[N * 2], ne[N * 2], tot = 1;
int f[N], fa[N];
bool flag[N];
int id[N];
int mask[N], cnt[N];
int g[1 << 22], way[1 << 22];
void add(int x, int y) {
ver[++tot] = y;
ne[tot] = head[x];
head[x] = tot;
}
void dfs(int x) {
for (int i = head[x]; i; i = ne[i]) {
int y = ver[i];
if (y == fa[x]) continue;
fa[y] = x;
dfs(y);
}
}
void dfs2(int x) {
if (flag[x]) f[x] |= 1 << id[x];
for (int i = head[x]; i; i = ne[i]) {
int y = ver[i];
if (y == fa[x]) continue;
f[y] |= f[x];
dfs2(y);
}
}
int main() {
int n, m, k;
scanf("%d%d%d", &n, &m, &k);
for (int i = 1; i < n; ++i) {
int x, y;
scanf("%d%d", &x, &y);
add(x, y), add(y, x);
}
dfs(1);
for (int i = 0; i < m; ++i) {
int t;
scanf("%d", &t);
int x = ver[t * 2], y = ver[t * 2 ^ 1];
if (fa[y] == x) swap(x, y);
flag[x] = true;
id[x] = i;
}
dfs2(1);
int siz = 0;
while (k--) {
int x, y;
scanf("%d%d", &x, &y);
int cur_mask = f[x] ^ f[y];
bool found = false;
for (int i = 0; i < siz; ++i) {
if (cur_mask == mask[i]) {
found = true;
cnt[i]++;
break;
}
}
if (!found) {
mask[siz] = cur_mask, cnt[siz] = 1;
siz++;
}
}
memset(g, 0x3f, sizeof(g));
g[0] = 0, way[0] = 1;
for (int i = 0; i < siz; ++i) {
for (int j = 0; j < (1 << m); ++j) {
if (g[j | mask[i]] > g[j] + 1) {
g[j | mask[i]] = g[j] + 1;
way[j | mask[i]] = (long long)way[j] * cnt[i] % MOD;
}
else if (g[j | mask[i]] == g[j] + 1) {
way[j | mask[i]] = (way[j | mask[i]] + (long long)way[j] * cnt[i] % MOD) % MOD;
}
}
}
if (way[(1 << m) - 1]) printf("%d %d\n", g[(1 << m) - 1], way[(1 << m) - 1]);
else printf("-1\n");
return 0;
}
2025牛客暑期多校训练营5
https://starlab.top/posts/25ncmu5/
作者
Star
发布于
2025-07-30
许可协议
CC BY-NC-SA 4.0

部分信息可能已经过时