图片来源
1605 字
8 分钟
2025春训第五场
又是熟悉的博弈问题,又是熟悉的做不出来,不过剩下三道能做出来的都挺有意思的,相对比较满足。
A. 游戏
答案= min(max(从小到大交替选,从大到小交替选),max(A从最大的连续选,A从最小的连续选)).
-
提示:A 希望答案尽可能大,所以由 A 决策决定的应取 max,B 希望答案尽可能小,所以由 B 决策决定的应该取 min.
-
建议:给 @xx liu (qwertyuiop) 佬磕一个
#include <iostream>#include <algorithm>
using namespace std;
int a[100010];
int main() { int n; scanf("%d", &n); for (int i = 1; i <= n; ++i) { scanf("%d", &a[i]); } sort(a + 1, a + n + 1); long long A = 0, B = 0; long long res; for (int i = n; i > 0; i -= 2) A += a[i]; for (int i = n - 1; i > 0; i -= 2) B += a[i]; res = abs(A) - abs(B); A = 0, B = 0; for (int i = 1; i <= n; i += 2) A += a[i]; for (int i = 2; i <= n; i += 2) B += a[i]; res = max(res, abs(A) - abs(B)); A = 0, B = 0; for (int i = 1; i <= n; ++i) { if (i <= (n + 1) / 2) A += a[i]; else B += a[i]; } long long t1 = abs(A) - abs(B); A = 0, B = 0; reverse(a + 1, a + n + 1); for (int i = 1; i <= n; ++i) { if (i <= (n + 1) / 2) A += a[i]; else B += a[i]; } res = min(max(t1, abs(A) - abs(B)), res); printf("%lld\n", res); return 0;}B. 音符
单调队列优化的 dp 效率最高, 或者线段树也行,两版代码都贴一下。
一个双指针维护内层 max,一个单调队列维护 ,就可以实现 O(n).
单调队列代码
#include <iostream>#include <algorithm>#include <map>
using namespace std;
const int N = 500010;int a[N], q[N];long long f[N];
int main() { int n, k; scanf("%d%d", &n, &k); for (int i = 1; i <= n; ++i) { scanf("%d", &a[i]); } long long res = 0; int pre_max = 0; sort(a + 1, a + n + 1); int hh = 0, tt = -1, t = 0; for (int i = 1, j = 1; i <= n; ++i) { while (hh <= tt && a[i] - a[q[hh]] > k) hh++; while (j <= i && a[i] - a[j] > k) j++; if (hh <= tt) res = max(res, i + f[q[hh]]); f[i] = -i + 1 + pre_max; pre_max = max(pre_max, i - j + 1); while (hh <= tt && f[i] >= f[q[tt]]) tt--; q[++tt] = i; } printf("%lld\n", res); return 0;}线段树暴戾代码(离散化疑似反向优化了)
#include <iostream>#include <cstring>#include <map>
using namespace std;
const int N = 500010;map<int, int> mp;int a[N], b[N];long long tr[N * 4], f[N][2], s[N];
void pushup(int u) { tr[u] = max(tr[u << 1], tr[u << 1 | 1]);}
void init(int u, int l, int r) { if (l == r) tr[u] = l == 0 ? 0 : -__LONG_LONG_MAX__; else { int mid = l + r >> 1; init(u << 1, l, mid), init(u << 1 | 1, mid + 1, r); pushup(u); }}
void modify(int u, int l, int r, int p, int v) { if (l == r) tr[u] = v; else { int mid = l + r >> 1; if (p <= mid) modify(u << 1, l, mid, p, v); else modify(u << 1 | 1, mid + 1, r, p, v); pushup(u); }}
long long query(int u, int l, int r, int ql, int qr) { if (ql <= l && r <= qr) return tr[u]; else { long long res = -__LONG_LONG_MAX__; int mid = l + r >> 1; if (ql <= mid) res = max(res, query(u << 1, l, mid, ql ,qr)); if (qr > mid) res = max(res, query(u << 1 | 1, mid + 1, r, ql, qr)); return res; }}
int main() { int n, k; scanf("%d%d", &n, &k); init(1, 0, n); for (int i = 1; i <= n; ++i) { scanf("%d", &a[i]); mp[a[i]]; } int m = 0; for (auto &i : mp) { i.second = ++m; b[m] = i.first; } for (int i = 1; i <= n; ++i) { s[mp[a[i]]]++; } for (int i = 1; i <= m; ++i) s[i] += s[i - 1]; long long res = 0; for (int i = 1; i <= m; ++i) { int l = 0, r = i - 1; while (l < r) { int mid = l + r + 1 >> 1; if (b[mid] < b[i] - k) l = mid; else r = mid - 1; } f[i][0] = max(f[i - 1][0], s[i] - s[l]); f[i][1] = s[i] + query(1, 0, n, l, i - 1); modify(1, 0, n, i, -s[i] + f[i][0]); } for (int i = 1; i <= m; ++i) { res = max(res, f[i][1]); } printf("%lld\n", res); return 0;}非常反直觉,其实我敲线段树比上面的单调队列快,单调队列比较考验思维,不像我们线段树和二分查找,直接背板子就行了。
C. 星星点灯
过滤掉所有边权大于 m 的边,然后跑最小生成树,同时统计边权和,最后加上 连通块个数 * m 就是答案。相对比较 easy.
#include <iostream>#include <algorithm>
using namespace std;
const int N = 1010;struct edge { int x, y, z;} ed[N * N];int fa[N];
int getfa(int x) { return x == fa[x] ? x : fa[x] = getfa(fa[x]);}
int main() { int val, n, m = 0; scanf("%d%d", &val, &n); for (int i = 1; i <= n; ++i) fa[i] = i; for (int i = 1; i <= n; ++i) { for (int j = 1; j <= n; ++j) { int t; scanf("%d", &t); if (i < j) ed[++m] = {i, j, t}; } } sort(ed + 1, ed + m + 1, [](edge a, edge b) { return a.z < b.z; }); long long res = 0; for (int i = 1; i <= m; ++i) { if (ed[i].z > val) break; int x = ed[i].x, y = ed[i].y; x = getfa(x), y = getfa(y); if (x == y) continue; else { fa[y] = x; res += ed[i].z; } } for (int i = 1; i <= n; ++i) if (i == fa[i]) res += val; printf("%lld\n", res); return 0;}D. 翘课
类似于分组背包问题,先把每一天单独的旷 [0, k] 节课的后呆在教学楼的时间算出来,然后跑分组背包dp即可。(理论上可以在维护每天单独的时间的同时做分组背包dp,但是我不知道为什么一直写挂)
- ps:说的很简单,但是内部逻辑其实有点绕。
#include <iostream>#include <cstring>#include <vector>
using namespace std;
vector<int> a[510];int f[510][510];int g[510][510];
int main() { int n, m, k; scanf("%d%d%d", &n, &m, &k); for (int i = 1; i <= n; ++i) { for (int j = 1; j <= m; ++j) { int t; scanf("%d", &t); if (t) a[i].push_back(j); } } memset(f, 0x3f, sizeof(f)); memset(g, 0x3f, sizeof(g)); for (int i = 1; i <= n; ++i) { for (int j = 0; j < a[i].size(); ++j) { // 旷课 j 节 int t = a[i].size() - j; // 上课 t 节 for (int k = t - 1; k < a[i].size(); ++k) { f[i][j] = min(f[i][j], a[i][k] - a[i][k - t + 1] + 1); } } f[i][a[i].size()] = 0; } g[0][0] = 0; for (int i = 1; i <= n; ++i) { for (int j = 0; j <= k; ++j) { for (int l = 0; l <= j; ++l) { g[i][j] = min(g[i][j], g[i - 1][l] + f[i][j - l]); } } } int res = 0x3f3f3f3f; for (int i = 0; i <= k; ++i) { res = min(res, g[n][i]); } printf("%d\n", res); return 0;}部分信息可能已经过时







