CSP-S 2022题解

1 Holiday

观察到 k100,很自然地想到 O(nmk64) 预处理出所有点在转 k 次车后可以到达的点集。

考虑枚举 B,C,计算可行的 A,D 中的最大值。

预处理 1 号点与其他点中 k 可达点集的前 3 大值,复杂度 O(n2)

简单合并 6 个值即可。

时间复杂度 O(nmk64+n2)

代码

Copy
#include <iostream> #include <cstdio> #include <cstring> #include <algorithm> #include <queue> #include <map> #include <set> #include <cstdlib> #include <cmath> #include <deque> #include <bitset> using namespace std; #define pii pair<int,int> #define mp make_pair const int N = 2505, M = 1e4 + 10, K = 105; #define int long long int read() { int x = 0, f = 1; char c = getchar(); while (c < '0' || c > '9') { if (c == '-') f = -1; c = getchar(); } while (c >= '0' && c <= '9') { x = (x << 1) + (x << 3) + (c ^ 48); c = getchar(); } return x * f; } int n, m, k; int a[N]; vector<int> v[N]; int maxn[N], maxx[N], maxxx[N]; bitset<N> b[N][K]; signed main() { n = read(), m = read(), k = read(); for (int i = 2; i <= n; i++) a[i] = read(); for (int i = 1; i <= m; i++) { int x = read(), y = read(); v[x].push_back(y), v[y].push_back(x); b[x][0].set(y), b[y][0].set(x); } for (int p = 1; p <= k; p++) { for (int i = 1; i <= n; i++) { b[i][p] = b[i][p - 1]; int siz = v[i].size(); for (int j = 0; j < siz; j++) { int x = v[i][j]; b[i][p] |= b[x][p - 1]; } } } for (int i = 1; i <= n; i++) { for (int j = 1; j <= n; j++) { if (i == j) continue; if (b[i][k][j] == 0 || b[1][k][j] == 0) continue; if (a[j] > a[maxn[i]]) maxxx[i] = maxx[i], maxx[i] = maxn[i], maxn[i] = j; else if (a[j] == a[maxn[i]]) maxxx[i] = maxx[i], maxx[i] = j; else if (a[j] > a[maxx[i]]) maxxx[i] = maxx[i], maxx[i] = j; else if (a[j] == a[maxx[i]]) maxxx[i] = j; else if (a[j] > a[maxxx[i]]) maxxx[i] = j; } } int ans = 0; for (int i = 1; i <= n; i++) { for (int j = 1; j <= n; j++) { if (i == j) continue; if (!b[i][k][j]) continue; int A = 0, B = 0; if (maxn[i] != j) A = maxn[i]; else if (maxx[i] != j) A = maxx[i]; else if (maxxx[i] != j) A = maxxx[i]; if (maxn[j] != i && maxn[j] != A) B = maxn[j]; else if (maxx[j] != i && maxx[j] != A) B = maxx[j]; else if (maxxx[j] != i && maxxx[j] != A) B = maxxx[j]; if (A && B) ans = max(ans, a[i] + a[j] + a[A] + a[B]); } } printf("%lld\n", ans); return 0; }

2 Game

简单分类讨论。

如果 L[l1,r1] 内的正数,Q[l2,r2] 内最小值最优。

此时 Q 所选的数一定,故 L 选择的要么是最大的正数,要么是最小的正数。

如果 L0,那么 Q 无论选什么结果都是 0

如果 L[l1,r1] 内的负数,Q[l2,r2] 内最大值最优。

此时 Q 所选的数仍然一定,故 L 选择的要么是最大的负数,要么是最小的负数。

维护 6st 表,分别表示 a 中区间最大正数,区间最小正数,区间最大负数,区间最小负数和 b 中区间最大值和区间最小值即可。

时间复杂度 O(nlogn+mlogm+q)

代码

Copy
#include <bits/stdc++.h> using namespace std; const int N = 1e5 + 10; typedef long long ll; #define int long long int read() { int x = 0, f = 1; char c = getchar(); while (c < '0' || c > '9') { if (c == '-') f = -1; c = getchar(); } while (c >= '0' && c <= '9') { x = (x << 1) + (x << 3) + (c ^ 48); c = getchar(); } return x * f; } int n, m, q; int a[N], b[N], sum[N]; struct stb1 { int st[N][20]; void init(int f) { if (f == 1) { for (int i = 1; i <= n; i++) st[i][0] = (a[i] > 0) ? a[i] : -1; for (int j = 1; j <= 19; j++) for (int i = 1; i + (1 << j) - 1 <= n; i++) st[i][j] = max(st[i][j - 1], st[i + (1 << (j - 1))][j - 1]); } else if (f == 2) { for (int i = 1; i <= n; i++) st[i][0] = (a[i] > 0) ? a[i] : 1e9 + 1; for (int j = 1; j <= 19; j++) for (int i = 1; i + (1 << j) - 1 <= n; i++) st[i][j] = min(st[i][j - 1], st[i + (1 << (j - 1))][j - 1]); } else if (f == 3) { for (int i = 1; i <= n; i++) st[i][0] = (a[i] < 0) ? a[i] : -1e9 - 1; for (int j = 1; j <= 19; j++) for (int i = 1; i + (1 << j) - 1 <= n; i++) st[i][j] = max(st[i][j - 1], st[i + (1 << (j - 1))][j - 1]); } else { for (int i = 1; i <= n; i++) st[i][0] = (a[i] < 0) ? a[i] : 1; for (int j = 1; j <= 19; j++) for (int i = 1; i + (1 << j) - 1 <= n; i++) st[i][j] = min(st[i][j - 1], st[i + (1 << (j - 1))][j - 1]); } } int query(int l, int r, int f) { int x = log2(r - l + 1); if (f == 1 || f == 3) return max(st[l][x], st[r - (1 << x) + 1][x]); return min(st[l][x], st[r - (1 << x) + 1][x]); } }T1, T2, T3, T4; struct stb2 { int st[N][20]; void init(int f) { for (int i = 1; i <= m; i++) st[i][0] = b[i]; if (f == 1) { for (int j = 1; j <= 19; j++) for (int i = 1; i + (1 << j) - 1 <= m; i++) st[i][j] = max(st[i][j - 1], st[i + (1 << (j - 1))][j - 1]); } else { for (int j = 1; j <= 19; j++) for (int i = 1; i + (1 << j) - 1 <= m; i++) st[i][j] = min(st[i][j - 1], st[i + (1 << (j - 1))][j - 1]); } } int query(int l, int r, int f) { int x = log2(r - l + 1); if (f == 1) return max(st[l][x], st[r - (1 << x) + 1][x]); return min(st[l][x], st[r - (1 << x) + 1][x]); } }T5, T6; signed main() { n = read(), m = read(), q = read(); for (int i = 1; i <= n; i++) a[i] = read(), sum[i] = sum[i - 1] + (a[i] == 0); for (int i = 1; i <= m; i++) b[i] = read(); T1.init(1); T2.init(2); T3.init(3); T4.init(4), T5.init(1), T6.init(2); for (int i = 1; i <= q; i++) { int l1 = read(), r1 = read(), l2 = read(), r2 = read(); int res = ll(-1e18) - 1; int A = T1.query(l1, r1, 1), B = T2.query(l1, r1, 2), C = T3.query(l1, r1, 3), D = T4.query(l1, r1, 4); int E = T5.query(l2, r2, 1), F = T6.query(l2, r2, 2); if (sum[r1] - sum[l1 - 1] > 0) res = 0; if (A != -1) res = max(res, A * F); if (B != 1e9 + 1) res = max(res, B * F); if (C != -1e9 - 1) res = max(res, C * E); if (D != 1) res = max(res, D * E); printf("%lld\n", res); } return 0; }

3 Galaxy

人类智慧题。

观察到一个性质:如果所有节点都可以实现连续穿越,那么所有据点都可以实现反击。

这是因为如果每个点都存在出边,那么一直沿出边走一定可以回到某个访问过的点。

问题转化为了判断所有节点的出度 degi 是否都为 1

考虑给每个节点随机赋予一个权值 ai,当前状态值为 i=1ndegiai

将判断是否成立的条件设为:当前状态值是否等于 i=1nai

根据感性理解,这个算法的正确率非常高。

于是我们只用记录 Si 表示所有当前存在指向 i 的边的点的 ai 之和,操作时简单计算当前状态值即可。

代码

Copy
#include <iostream> #include <cstdio> #include <cstring> #include <algorithm> #include <queue> #include <map> #include <set> #include <cstdlib> #include <cmath> #include <deque> using namespace std; #define pii pair<int,int> #define mp make_pair const int N = 5e5 + 10; typedef long long ll; #define int long long int read() { int x = 0, f = 1; char c = getchar(); while (c < '0' || c > '9') { if (c == '-') f = -1; c = getchar(); } while (c >= '0' && c <= '9') { x = (x << 1) + (x << 3) + (c ^ 48); c = getchar(); } return x * f; } int n, m, tot, ans, sum; int a[N], head[N], ver[N * 2], last[N * 2], S[N], cur[N]; void add(int x, int y) { ver[++tot] = y; last[tot] = head[x]; head[x] = tot; } signed main() { srand(time(NULL)); n = read(), m = read(); for (int i = 1; i <= n; i++) a[i] = rand() % (ll)(1e10) + 1, sum += a[i]; for (int i = 1; i <= m; i++) { int u = read(), v = read(); S[v] += a[u]; cur[v] += a[u]; ans += a[u]; } int q = read(); for (int i = 1; i <= q; i++) { int opt = read(); if (opt == 1) { int u = read(), v = read(); cur[v] -= a[u]; ans -= a[u]; } if (opt == 2) { int u = read(); ans -= cur[u]; cur[u] = 0; } if (opt == 3) { int u = read(), v = read(); cur[v] += a[u]; ans += a[u]; } if (opt == 4) { int u = read(); ans += S[u] - cur[u]; cur[u] = S[u]; } if (ans == sum) puts("YES"); else puts("NO"); } return 0; }

4 Transmit

简单矩阵乘法。

k=1 时,询问为路径上点权之和,简单 LCA 即可。

k=2 时,将路径看作一个序列。设 fi 表示 i 强制选择时,前 i 个主机花费时间的最小值。

显然有 fi=min{fi1,fi2}+ai

对于 n,q2000 以及数据随机的部分分,设 len 为期望链长,O(q×len) 的暴力 dp 复杂度是可以接受的。

容易发现,min+ 存在矩阵乘法。

设初始矩阵为 [fifi+1],那么转移矩阵为 [INFai+20ai+2]

也就是说:

[fifi+1][INFai+20ai+2]=[fi+1fi+2]

其中 表示新定义的矩阵乘法,满足对于 a×b 的矩阵 Ab×c 的矩阵 B,有 AB=CCi,j=mink=1bAi,k+Bk,j

容易证明,该矩阵乘法满足结合律 ABC=A(BC),但值得注意的是,它并不满足交换律。

有了结合律后,我们可以先将路径上第 i+2 及以后项元素的转移矩阵依次相乘,再与初始矩阵相乘。

维护路径上所有转移矩阵的乘积可以用倍增实现。具体地,设 gx,i 表示 x 开始,向上 2i 个祖先的转移矩阵从下到上的乘积。查询时直接倍增乘在一起即可。

需要注意的是,矩阵乘法并不满足交换律,也就是说我们需要记录 Gx,i 表示 x 开始,向上 2i 个祖先的转移矩阵从上到下的乘积,查询时倒着倍增。

k=3 时,不能简单地令 fi=min{fi2,fi1,fi},因为可能有一步跳出链后沿着距离路径为 1 的若干点连续跳跃,最后跳回链上。

比如当图为 12,23,34,35,56,57,78 时,18 的路径可以由 1468 组成。

容易发现,只有在跳到距离链为 1 的点上的时候可能更优,否则相当于白走了一个点。

修改一下我们的状态:fi,0/1 表示 i 强制选择/i 相邻的不在链上的点强制选择,花费时间的最小值。

bi 为与 i 相邻且不在链上的点的权值的最小值,那么有转移:

fi,0=min{fi1,0,fi2,0,fi3,0,fi2,1}+ai,fi,1=min{fi2,0,fi1,1}+bi

此时的初始矩阵为 [fi,0fi,1fi+1,0fi+1,1fi+2,0fi+2,1],转移矩阵为:

[INFINFINFINFai+3INFINFINFINFINFINFINF0INFINFINFai+3bi+3INF0INFINFai+3INFINFINF0INFai+3INFINFINFINF0INFbi+3]

维护方法与之前类似。

但此时有了新的问题:占用的空间太大了。

6×6×2×105×18×2×81024×10241977MB

此时再次观察上面的转移矩阵,发现有一整行的转移都是 INF

于是我们抛弃掉 fi,1,将矩阵改写为 5×5 的。

[INFINFINFai+3INF0INFINFai+3bi+3INFINFINFai+3INFINF0INFai+3INFINFINF0INFbi+3]

此时的空间为 5×5×2×105×18×2×81024×10241373MB

还是超过了空间限制,但是注意到 G 数组其实没有必要,在第一次利用完 g 后将询问离线,将 G 的信息存储在 g 内再跑一遍即可。

此时的空间为 5×5×2×105×18×81024×1024686MB

时间复杂度 O(53nlogn+53mlogn)

代码

Copy
#include <iostream> #include <cstdio> #include <cstring> #include <algorithm> #include <queue> #include <map> #include <set> #include <cstdlib> #include <cmath> #include <deque> using namespace std; #define pii pair<int,int> #define mp make_pair const int N = 2e5 + 1; typedef long long ll; int read() { int x = 0, f = 1; char c = getchar(); while (c < '0' || c > '9') { if (c == '-') f = -1; c = getchar(); } while (c >= '0' && c <= '9') { x = (x << 1) + (x << 3) + (c ^ 48); c = getchar(); } return x * f; } int n, Q, k, tot, cnt; int head[N], ver[N * 2], last[N * 2], a[N], b[N], f[N][18], dep[N], c[N], u[N], v[N]; ll dp[N][3]; pii Cur[N]; struct Matrix { ll a[5][5]; void init() {for (int i = 0; i < 5; i++) for (int j = 0; j < 5; j++) a[i][j] = 0x3f3f3f3f3f3f3f3f;} void one() {init(); for (int i = 0; i < 5; i++) a[i][i] = 0;} void print() { for (int i = 0; i < 5; i++) { for (int j = 0; j < 5; j++) { cout << a[i][j] << ' '; } cout << endl; } } }g[N][18]; Matrix ans[N]; Matrix operator *(const Matrix &a, const Matrix &b) { Matrix res; res.init(); for (int i = 0; i < 5; i++) { for (int j = 0; j < 5; j++) { for (int p = 0; p < 5; p++) res.a[i][j] = min(res.a[i][j], a.a[i][p] + b.a[p][j]); } } return res; } void add(int x, int y) { ver[++tot] = y; last[tot] = head[x]; head[x] = tot; } int lca(int x, int y) { if (dep[x] < dep[y]) swap(x, y); for (int i = 17; i >= 0; i--) if (dep[f[x][i]] >= dep[y]) x = f[x][i]; if (x == y) return x; for (int i = 17; i >= 0; i--) {if (f[x][i] != f[y][i]) x = f[x][i], y = f[y][i];} return f[x][0]; } void dfs(int x, int fa) { dep[x] = dep[fa] + 1; for (int i = head[x]; i; i = last[i]) { int y = ver[i]; if (y == fa) continue; f[y][0] = x; for (int j = 1; j <= 17; j++) f[y][j] = f[f[y][j - 1]][j - 1]; g[y][0].init(); g[y][0].a[1][0] = 0; g[y][0].a[3][1] = 0; g[y][0].a[4][2] = 0; if (k == 3) g[y][0].a[0][3] = g[y][0].a[2][3] = a[y], g[y][0].a[1][4] = g[y][0].a[4][4] = b[y]; if (k >= 2) g[y][0].a[1][3] = a[y]; g[y][0].a[3][3] = a[y]; for (int j = 1; j <= 17 && f[y][j - 1]; j++) g[y][j] = g[y][j - 1] * g[f[y][j - 1]][j - 1]; dfs(y, x); } } void dfs2(int x, int fa) { for (int i = head[x]; i; i = last[i]) { int y = ver[i]; if (y == fa) continue; for (int j = 1; j <= 17 && f[y][j - 1]; j++) g[y][j] = g[f[y][j - 1]][j - 1] * g[y][j - 1]; dfs2(y, x); } } Matrix get2(int x, int y) { Matrix res; res.one(); for (int i = 17; i >= 0; i--) if (dep[f[x][i]] >= dep[y]) res = res * g[x][i], x = f[x][i]; return res; } Matrix get(int x, int y) { Matrix res; res.one(); int cnt2 = 0; for (int i = 17; i >= 0; i--) if (dep[f[x][i]] >= dep[y]) Cur[++cnt2] = mp(x, i), x = f[x][i]; for (int i = cnt2; i >= 1; i--) res = res * g[Cur[i].first][Cur[i].second]; return res; } signed main() { n = read(), Q = read(), k = read(); for (int i = 1; i <= n; i++) a[i] = read(); for (int i = 1; i < n; i++) { int u = read(), v = read(); add(u, v); add(v, u); } for (int x = 1; x <= n; x++) { b[x] = 0x3f3f3f3f; for (int i = head[x]; i; i = last[i]) b[x] = min(b[x], a[ver[i]]); } g[1][0].init(); g[1][0].a[1][0] = 0; g[1][0].a[3][1] = 0; g[1][0].a[4][2] = 0; if (k == 3) g[1][0].a[0][3] = g[1][0].a[2][3] = a[1], g[1][0].a[1][4] = g[1][0].a[4][4] = b[1]; if (k >= 2) g[1][0].a[1][3] = a[1]; g[1][0].a[3][3] = a[1]; dfs(1, 0); for (int i = 1; i <= Q; i++) { u[i] = read(), v[i] = read(); int L = lca(u[i], v[i]); if (dep[u[i]] - dep[L] < dep[v[i]] - dep[L]) swap(u[i], v[i]); if (dep[u[i]] - dep[L] <= 3) continue; int p = u[i]; for (int j = 1; j <= 3; j++) p = f[p][0]; ans[i] = get2(p, L) * g[L][0]; } dfs2(1, 0); for (int i = 1; i <= Q; i++) { int L = lca(u[i], v[i]); if (dep[u[i]] - dep[L] < dep[v[i]] - dep[L]) swap(u[i], v[i]); if (dep[u[i]] - dep[L] <= 3) { int pos; cnt = 0; while (u[i] != L) c[++cnt] = u[i], u[i] = f[u[i]][0]; c[++cnt] = L; pos = cnt; while (v[i] != L) c[++cnt] = v[i], v[i] = f[v[i]][0]; for (int j = pos + 1; j <= cnt; j++) if (j > (cnt - (j - pos) + 1)) swap(c[j], c[cnt - (j - pos) + 1]); dp[1][0] = a[c[1]]; dp[1][1] = 0x3f3f3f3f3f3f3f3f; for (int j = 2; j <= cnt; j++) dp[j][0] = dp[j][1] = 0x3f3f3f3f3f3f3f3f; for (int j = 2; j <= cnt; j++) { for (int p = 1; p <= k; p++) if (j > p) dp[j][0] = min(dp[j][0], dp[j - p][0]); if (k == 3 && j > 2) { dp[j][0] = min(dp[j][0], dp[j - 2][1]); dp[j][1] = min(dp[j][1], dp[j - 1][1]); dp[j][1] = min(dp[j][1], dp[j - 2][0]); } dp[j][0] += a[c[j]]; dp[j][1] += b[c[j]]; } printf("%lld\n", dp[cnt][0]); continue; } cnt = 0; for (int j = 1; j <= 3; j++) c[++cnt] = u[i], u[i] = f[u[i]][0]; dp[1][0] = a[c[1]]; dp[1][1] = 0x3f3f3f3f3f3f3f3f; for (int j = 2; j <= cnt; j++) dp[j][0] = dp[j][1] = 0x3f3f3f3f3f3f3f3f; for (int j = 2; j <= cnt; j++) { for (int p = 1; p <= k; p++) if (j > p) dp[j][0] = min(dp[j][0], dp[j - p][0]); if (k == 3 && j > 2) { dp[j][0] = min(dp[j][0], dp[j - 2][1]); dp[j][1] = min(dp[j][1], dp[j - 1][1]); dp[j][1] = min(dp[j][1], dp[j - 2][0]); } dp[j][0] += a[c[j]]; dp[j][1] += b[c[j]]; } ans[i] = ans[i] * get(v[i], L); Matrix cur; cur.init(); cur.a[0][0] = dp[1][0], cur.a[0][1] = dp[2][0], cur.a[0][2] = dp[2][1], cur.a[0][3] = dp[3][0], cur.a[0][4] = dp[3][1]; cur = cur * ans[i]; printf("%lld\n", cur.a[0][3]); } return 0; }

欢迎关注我的公众号

posted @   David24  阅读(516)  评论(6编辑  收藏  举报
相关博文:
阅读排行:
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 【译】Visual Studio 中新的强大生产力特性
· 【设计模式】告别冗长if-else语句:使用策略模式优化代码结构
· AI与.NET技术实操系列(六):基于图像分类模型对图像进行分类
点击右上角即可分享
微信分享提示