CSP-S 2022题解
1
观察到 ,很自然地想到 预处理出所有点在转 次车后可以到达的点集。
考虑枚举 ,计算可行的 中的最大值。
预处理 号点与其他点中 可达点集的前 大值,复杂度 。
简单合并 个值即可。
时间复杂度
代码
#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
简单分类讨论。
如果 选 内的正数, 选 内最小值最优。
此时 所选的数一定,故 选择的要么是最大的正数,要么是最小的正数。
如果 选 ,那么 无论选什么结果都是 。
如果 选 内的负数, 选 内最大值最优。
此时 所选的数仍然一定,故 选择的要么是最大的负数,要么是最小的负数。
维护 个 表,分别表示 中区间最大正数,区间最小正数,区间最大负数,区间最小负数和 中区间最大值和区间最小值即可。
时间复杂度 。
代码
#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
人类智慧题。
观察到一个性质:如果所有节点都可以实现连续穿越,那么所有据点都可以实现反击。
这是因为如果每个点都存在出边,那么一直沿出边走一定可以回到某个访问过的点。
问题转化为了判断所有节点的出度 是否都为 。
考虑给每个节点随机赋予一个权值 ,当前状态值为 。
将判断是否成立的条件设为:当前状态值是否等于 。
根据感性理解,这个算法的正确率非常高。
于是我们只用记录 表示所有当前存在指向 的边的点的 之和,操作时简单计算当前状态值即可。
代码
#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
简单矩阵乘法。
时,询问为路径上点权之和,简单 即可。
时,将路径看作一个序列。设 表示 强制选择时,前 个主机花费时间的最小值。
显然有 。
对于 以及数据随机的部分分,设 为期望链长, 的暴力 复杂度是可以接受的。
容易发现, 对 存在矩阵乘法。
设初始矩阵为 ,那么转移矩阵为 。
也就是说:
其中 表示新定义的矩阵乘法,满足对于 的矩阵 和 的矩阵 ,有 。
容易证明,该矩阵乘法满足结合律 ,但值得注意的是,它并不满足交换律。
有了结合律后,我们可以先将路径上第 及以后项元素的转移矩阵依次相乘,再与初始矩阵相乘。
维护路径上所有转移矩阵的乘积可以用倍增实现。具体地,设 表示 开始,向上 个祖先的转移矩阵从下到上的乘积。查询时直接倍增乘在一起即可。
需要注意的是,矩阵乘法并不满足交换律,也就是说我们需要记录 表示 开始,向上 个祖先的转移矩阵从上到下的乘积,查询时倒着倍增。
时,不能简单地令 ,因为可能有一步跳出链后沿着距离路径为 的若干点连续跳跃,最后跳回链上。
比如当图为 时, 的路径可以由 组成。
容易发现,只有在跳到距离链为 的点上的时候可能更优,否则相当于白走了一个点。
修改一下我们的状态: 表示 强制选择与 相邻的不在链上的点强制选择,花费时间的最小值。
设 为与 相邻且不在链上的点的权值的最小值,那么有转移:
。
此时的初始矩阵为 ,转移矩阵为:
维护方法与之前类似。
但此时有了新的问题:占用的空间太大了。
。
此时再次观察上面的转移矩阵,发现有一整行的转移都是 。
于是我们抛弃掉 ,将矩阵改写为 的。
此时的空间为 。
还是超过了空间限制,但是注意到 数组其实没有必要,在第一次利用完 后将询问离线,将 的信息存储在 内再跑一遍即可。
此时的空间为 。
时间复杂度 。
代码
#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;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 【译】Visual Studio 中新的强大生产力特性
· 【设计模式】告别冗长if-else语句:使用策略模式优化代码结构
· AI与.NET技术实操系列(六):基于图像分类模型对图像进行分类