2025牛客暑期多校训练营4

B. Blind Alley

题意:在一个迷宫里,从\((1, 1)\)出发,只能向上向下和向右走,且不能走墙。目的是走到\((1, m)\)。如果走到了第\(j\)列,那么可以看到\([j, \min(j + k, m)]\)这些列的状态。求有没有一个位置,使得其不能到达终点,但从起点到这个点的路径上除了\((i, j)\)的所有点的视野都无法判断这个点不能到达终点。

\(r[i][j]\)\((i, j)\)可以到的最右边的列,那么如果\((i, j)\)可以从\((1, 1)\)到达,且\(r[i][j] \geq j + k\)。则可以走到\((i, j)\)这个位置,而不知道这个位置不能到终点。
\(r[i][j]\)可以预处理,从后往前\(dp\)就行。

点击查看代码
#include <bits/stdc++.h>using i64 = long long;void solve() {int n, m, k;std::cin >> n >> m >> k;std::vector<std::string> s(n);for (int i = 0; i < n; ++ i) {std::cin >> s[i];}std::vector r(n, std::vector<int>(m));for (int i = 0; i < n; ++ i) {r[i][m - 1] = m - 1;}for (int j = m - 2; j >= 0; -- j) {for (int i = 0; i < n; ++ i) {r[i][j] = j;}for (int i = 0; i < n; ++ i) {if (s[i][j] == '0') {r[i][j] = std::max(r[i][j], r[i][j + 1]);}}for (int i = 1; i < n; ++ i) {if (s[i][j] == '0') {r[i][j] = std::max(r[i][j], r[i - 1][j]);}}for (int i = n - 2; i >= 0; -- i) {if (s[i][j] == '0') {r[i][j] = std::max(r[i][j], r[i + 1][j]);}}}std::vector f(n, std::vector<int>(m));for (int i = 0; i < n; ++ i) {f[i][0] = 1;}for (int j = 1; j + k - 1 < m; ++ j) {for (int i = 0; i < n; ++ i) {if (s[i][j] == '0') {f[i][j] |= f[i][j - 1];}}for (int i = 1; i < n; ++ i) {if (s[i][j] == '0') {f[i][j] |= f[i - 1][j];}}for (int i = n - 2; i >= 0; -- i) {if (s[i][j] == '0') {f[i][j] |= f[i + 1][j];	}}for (int i = 0; i < n; ++ i) {if (s[i][j] == '0' && f[i][j] && r[i][j] >= j + k && r[i][j] < m - 1) {std::cout << "Yes\n";return;}}}std::cout << "No\n";
}int main() {std::ios::sync_with_stdio(false), std::cin.tie(0), std::cout.tie(0);int t = 1;std::cin >> t;while (t -- ) {solve();}return 0;
}

D. Determinant of 01-Matrix

赛后补题
题意:构造一个行列式,使得它的值为\(n\)

构造太难,而且线代早忘了。。。
参考题解,大意就是第一行作为符号,然后下面构造一些等于\(2\)和等于\(1\)的位置,就可以用二进制一位一位构造出\(n\)来。

点击查看代码
#include <bits/stdc++.h>using i64 = long long;const int N = 29;
int a[210][210];void solve() {for (int i = 0; i < N; ++ i) {a[4 * i + 1][4 * i] = 1;a[4 * i + 1][4 * i + 1] = 1;a[4 * i + 2][4 * i + 1] = 1;a[4 * i + 2][4 * i + 2] = 1;a[4 * i + 3][4 * i] = 1;a[4 * i + 3][4 * i + 2] = 1;a[4 * i + 3][4 * i + 3] = 1;a[4 * i + 4][4 * i + 3] = 1;a[4 * i + 4][4 * i + 4] = 1;}int n;std::cin >> n;for (int i = 0; i <= N; ++ i) {if (n >> i & 1) {a[0][4 * i] = 1;}}std::cout << 4 * N + 1 << "\n";for (int i = 0; i <= 4 * N; ++ i) {for (int j = 0; j <= 4 * N; ++ j) {std::cout << a[i][j] << " \n"[j == 4 * N];}}
}int main() {std::ios::sync_with_stdio(false), std::cin.tie(0), std::cout.tie(0);int t = 1;// std::cin >> t;while (t -- ) {solve();}return 0;
}

F. For the Treasury!

题意:一个数组取前\(k\)个数的和为价值。你每次可以移动相邻两个数,代价为\(c\),求可以得到的最大价值。

如果想让第\(i\)个数移动后面去,让\(j\)从后面进来,可以看作\(j\)移动到\(k+1\),然后\(i\)也移动到\(k+1\)。那么记原本价值为\(sum\),对于\(i\leq k\),移动第\(i\)个数代价为\(a[i] + (k-i+1)\times c\);对于\(i>k\),贡献为\(a[i] - (i-k+1)\times c\)。我们每次选一对交换,选前面代价最小的和后面贡献最大的。

点击查看代码
#include <bits/stdc++.h>using i64 = long long;void solve() {int n, k;i64 c;std::cin >> n >> k >> c;std::vector<i64> a(n);for (int i = 0; i < n; ++ i) {std::cin >> a[i];}std::vector<i64> A, B;i64 ans = std::accumulate(a.begin(), a.begin() + k, 0ll);for (int i = 0; i < k; ++ i) {A.push_back(a[i] + (k - i) * c);}for (int i = k; i < n; ++ i) {B.push_back(a[i] - (i - k) * c);}std::ranges::sort(A);std::ranges::sort(B, std::greater<>());i64 sum = ans;for (int i = 0; i < A.size() && i < B.size(); ++ i) {sum += -A[i] + B[i];ans = std::max(ans, sum);}std::cout << ans << "\n";
}int main() {std::ios::sync_with_stdio(false), std::cin.tie(0), std::cout.tie(0);int t = 1;// std::cin >> t;while (t -- ) {solve();}return 0;
}

G. Ghost in the Parentheses

赛后补题。
题意:一个括号序列,每个位置可能变成未知的,对于未知的地方随便填,求有多少情况只能填出来原序列这一种合法的括号序列。

经典技巧是把'('看作\(1\),')'看作\(-1\),那么一个合法括号序列的充要条件是,每一个前缀都大于等于\(0\)且总和为\(0\)
考虑有哪些位置未知只能填出原序列。
满足条件的序列有两个条件:

  1. 左边若干个'('和右边若干个')'组成
  2. 交换最后的'('和最前的')'会使得没有括号序列合法。

如果不满足条件一,则交换一个')'和在它左边的'(',只会使得一些前缀和变大,那么得到的序列依然合法,也就是可以得到其它合法的序列。
考虑条件二的情况是\(((((...))))\)的情况,只要交换最后的')'和最前的'('非法,则只能填出原序列。因为拿一个更前的'('来交换,影响的前缀更多,更容易出现不合法的情况,而我们考虑的是有没有一种方式导致这个序列合法,也就是会有导致合法序列出现,那么希望观察影响前缀最小的情况,如果这个情况也无法产生其它的合法序列,则只能填出来原序列。
有人可能不懂为什么要交换两个括号,因为我们希望选出来的序列随便填也只能得到原序列一种,那么显然左括号右括号个数和原来不一样的填法肯定不合法,那么如果个数还是和原来一样,就可以看作有些括号进行了交换,显然交换相同括号没有意义,所以我们讨论交换左右括号的贡献。
那么对于交换的一对\(i, j\)\(i\)的位置本来贡献是\(1\),现在贡献是\(-1\),那么\([i, j)\)这一段的前缀和都要减\(2\),则只需要存在一个\(k\in [i, j)\),满足\(sum[k] < 2\)就行。
实际实现中,我们只需要枚举\(k\)就行了,那么记\(pre, suf\)分别为前缀左括号个数和后缀右括号个数,则有\(2^{pre_k} \times 2^{suf_{k+1}}\)中选法,但发现不同的\(k\)会重复计算一些区间,可以记\(pre\_cnt\)为上一个\(k\)的左括号的个数,\(now\)为上一个\(k\)到当前\(k\)之间的左括号的个数,然后我们不计算全不选的情况,贡献为\(2^{pre\_cnt} \times (2^{now}- 1) \times 2^{suf_{k+1}}\)。最后我们这里是没有计算全是右括号的情况的,已经需要加上不选的情况,那么需要再加上\(2^{\frac{n}{2}}+1\)
代码省略取模类。

点击查看代码
constexpr int P = 998244353;
using Z = MInt<P>;void solve() {std::string s;std::cin >> s;int n = s.size();std::vector<int> sum(n + 1);for (int i = 0; i < n; ++ i) {sum[i + 1] = sum[i] + (s[i] == '(' ? 1 : -1);}std::vector<int> suf(n + 2);for (int i = n - 1; i >= 0; -- i) {suf[i + 1] = suf[i + 2] + (s[i] == ')');}std::vector<Z> p(n + 1);p[0] = 1;for (int i = 1; i <= n; ++ i) {p[i] = p[i - 1] * 2;}Z ans = 0;int pre = 0, now = 0;for (int i = 1; i <= n; ++ i) {now += s[i - 1] == '(';if (sum[i] < 2) {ans += p[pre] * (p[now] - 1) * p[suf[i + 1]];pre += now;now = 0;}}ans += p[n / 2];std::cout << ans / p[n] << "\n";
}int main() {std::ios::sync_with_stdio(false), std::cin.tie(0), std::cout.tie(0);int t = 1;// std::cin >> t;while (t -- ) {solve();}return 0;
}

I. I, Box

题意:推箱子,不过箱子可以自己左右移动。

无解的情况就是一个联通块里箱子和目标个数不一样。
然后就是暴力模拟了,我是用三个\(set\)分别存没到目标的箱子的位置、没有箱子的目标位置、有箱子的目标位置。
然后每次从第二个集合里拿一个\(bfs\)找第一个集合里最近的。记录路径然后模拟,如果遇到第三个集合的位置,则总体往前推,再从最后一个箱子继续推。

点击查看代码
#include <bits/stdc++.h>using i64 = long long;struct DSU {std::vector<int> fa, cnt;DSU();DSU(int n) {init(n);}void init(int n) {fa.assign(n + 1, 0);cnt.assign(n + 1, 1);std::ranges::iota(fa, 0);}int find(int x) {return x == fa[x] ? x : fa[x] = find(fa[x]);}bool merge(int x, int y) {int u = find(x), v = find(y);if (u == v) {return false;}fa[v] = u;cnt[u] += cnt[v];return true;}bool same(int u, int v) {return find(u) == find(v);}int size(int u) {return cnt[find(u)];}
};const int dx[] = {-1, 0, 1, 0}, dy[] = {0, 1, 0, -1};
int st[55][55];
std::pair<int, int> pre[55][55];void solve() {int n, m;std::cin >> n >> m;std::vector<std::string> s(n);for (int i = 0; i < n; ++ i) {std::cin >> s[i];}std::set<std::pair<int, int>> a, b, c;DSU dsu(n * m);for (int i = 0; i < n; ++ i) {for (int j = 0; j < m; ++ j) {if (s[i][j] == '@') {a.emplace(i, j);} else if (s[i][j] == '*') {b.emplace(i, j);} else if (s[i][j] == '!') {c.emplace(i, j);}if (s[i][j] == '#') {continue;}for (int k = 0; k < 4; ++ k) {int x = i + dx[k], y = j + dy[k];if (x < 0 || x >= n || y < 0 || y >= m || s[x][y] == '#') {continue;}dsu.merge(i * m + j, x * m + y);}}}if (a.empty()) {std::cout << 0 << "\n";return;}{auto get = [&](const std::pair<int, int> & a) -> int {return a.first * m + a.second;};std::vector<int> cnta(n * m), cntb(n * m);for (auto & it : a) {++ cnta[dsu.find(get(it))];}for (auto & it : b) {++ cntb[dsu.find(get(it))];}for (int i = 0; i < n * m; ++ i) {if (cnta[i] != cntb[i]) {std::cout << -1 << "\n";return;}}}std::vector<std::tuple<int, int, char>> ans;auto work = [&](int sx, int sy) -> void {std::queue<std::pair<int, int>> q;memset(st, 0, sizeof st);pre[sx][sy] = {-1, -1};st[sx][sy] = 1;q.emplace(sx, sy);int tx, ty;while (q.size()) {auto [x, y] = q.front(); q.pop();if (a.count({x, y})) {tx = x, ty = y;break;}for (int i = 0; i < 4; ++ i) {int nx = x + dx[i], ny = y + dy[i];if (nx < 0 || nx >= n || ny < 0 || ny >= m || s[nx][ny] == '#' || st[nx][ny]) {continue;}pre[nx][ny] = {x, y};st[nx][ny] = 1;q.emplace(nx, ny);}}std::vector<std::pair<int, int>> path;a.erase({tx, ty});while (tx != sx || ty != sy) {path.emplace_back(tx, ty);auto & [nx, ny] = pre[tx][ty];tx = nx;ty = ny;}path.emplace_back(sx, sy);auto getop = [&](const std::pair<int, int> & a, const std::pair<int, int> & b) -> char {int x = a.first - b.first, y = a.second - b.second;if (x == -1 && y == 0) {return 'D';} else if (x == 1 && y == 0) {return 'U';} else if (x == 0 && y == -1) {return 'R';} else {return 'L';}};for (int i = 0; i + 1 < path.size(); ++ i) {if (c.count(path[i + 1])) {int j = i + 1;while (c.count(path[j + 1])) {++ j;}for (int k = j; k >= i; -- k) {ans.emplace_back(path[k].first, path[k].second, getop(path[k], path[k + 1]));}i = j;} else {ans.emplace_back(path[i].first, path[i].second, getop(path[i], path[i + 1]));}}c.emplace(sx, sy);b.erase({sx, sy});};while (b.size()) {auto & [x, y] = *b.begin();work(x, y);}std::cout << ans.size() << "\n";for (auto & [x, y, op] : ans) {std::cout << x + 1 << " " << y + 1 << " " << op << "\n";}
}int main() {std::ios::sync_with_stdio(false), std::cin.tie(0), std::cout.tie(0);int t = 1;// std::cin >> t;while (t -- ) {solve();}return 0;
}