写在前面

我竟然做过这套题还写过​​题解​

这次主要是补充一下 ​​T2​​ 和我 ​​T3​​ 的乱搞做法。

​T1​​ 不再赘述。

得分情况

预计得分:\(100 + 0 \sim 100 + 0 \sim 100 = 100 \sim 300\)。

实际得分:\(100 + 70 + 100 = 270\)。

考场上

开 T1,签到题。

开 T2,草,好 TM 眼熟啊,但是我不会,光知道要二分 L,不会 check。

开 T3,严格次短路,不会,写玄学算法希望多骗点分。

T2 神光

设 \(f_{i,j}\) 表示用了 \(i\) 次红光和 \(j\) 次绿光后第一个没被破坏的法坛最远在哪。

状态转移:

​f[0][0] = 第一个法坛的位置​

在已知左端点和 ​​L​​ 的时候能用 ​​upper_bound()​​ 推出下次的左端点。

​f[i][j] = max(a[upper_bound(排好序的法坛,f[i - 1][j] + x - 1) - 数组名],a[upper_bound(排好序的法坛,f[i][j - 1] + 2 * x - 1) - 数组名])​

转移的时候注意一下边界。

时间复杂度:\(O(30 n^2logn)\)。

Code:

#include <cstdio>
#include <cstring>
#include <string>
#include <iostream>
#include <algorithm>
#define M 2001
#define inf 1061109567

typedef long long ll;
int min(int a, int b) { return a < b ? a : b; }
int max(int a, int b) { return a > b ? a : b; }
inline void read(int &T) {
int x = 0; bool f = 0; char c = getchar();
while (c < '0' || c > '9') { if (c == '-') f = !f; c = getchar(); }
while (c >= '0' && c <= '9') { x = x * 10 + c - '0'; c = getchar(); }
T = f ? -x : x;
}

int n, r, g, a[M], f[M][M];

bool check(int x) {
f[0][0] = a[1];
int s1 = min(r, n), s2 = min(g, n);
for (int i = 0; i <= s1; ++i) {
for (int j = 0; j <= s2; ++j) {
if (i == 0 && j > 0) {
int pos = std::upper_bound(a + 1, a + n + 1, f[i][j - 1] + 2 * x - 1) - a;
if (pos > n) return true;
else f[i][j] = a[pos];
}
if (j == 0 && i > 0) {
int pos = std::upper_bound(a + 1, a + n + 1, f[i - 1][j] + x - 1) - a;
if (pos > n) return true;
else f[i][j] = a[pos];
}
if (i > 0 && j > 0) {
int pos = max(std::upper_bound(a + 1, a + n + 1, f[i - 1][j] + x - 1) - a, std::upper_bound(a + 1, a + n + 1, f[i][j - 1] + 2 * x - 1) - a);
if (pos > n) return true;
else f[i][j] = a[pos];
}
}
}
return false;
}

int main() {
read(n), read(r), read(g);
for (int i = 1; i <= n; ++i) read(a[i]);
std::sort(a + 1, a + n + 1);
int L = 1, R = a[n] + 1;
while (L <= R) {
int mid = (L + R) >> 1;
if (check(mid)) R = mid - 1;
else L = mid + 1;
}
printf("%d\n", L);
return 0;
}


上面复杂度估计要跑十几秒,想办法看能不能搞掉一个 \(log\)。

可以发现上面使用 ​​upper_bound()​​ 来转移的时候可能做了重复的工作,考虑能否预处理然后转移的时候直接调用。

发现位置的数字太大了上限是 \(10^9\),不好搞,于是我们令 \(f_{i,j}\) 表示用了 \(i\) 次红光和 \(j\) 次绿光第一个没被摧毁的法坛的编号,知道了编号自然知道了位置。

于是我们可以 \(n^2\) 来枚举左端点法坛的编号和右端点法坛的编号来判断能否全部摧毁,即预处理。

时间复杂度:\(O(30 n^2)\)

Code:

#include <cstdio>
#include <cstring>
#include <string>
#include <iostream>
#include <algorithm>
#define M 2001

typedef long long ll;
int min(int a, int b) { return a < b ? a : b; }
int max(int a, int b) { return a > b ? a : b; }
inline void read(int &T) {
int x = 0; bool f = 0; char c = getchar();
while (c < '0' || c > '9') { if (c == '-') f = !f; c = getchar(); }
while (c >= '0' && c <= '9') { x = x * 10 + c - '0'; c = getchar(); }
T = f ? -x : x;
}

int l1[M], l2[M];
int n, r, g, a[M], f[M][M];

bool check(int x) {
l1[0] = 1, l2[0] = 1;
for (int i = 1; i <= n; ++i) {
for (int j = 1; j <= n; ++j) {
if (a[j] <= a[i] + x - 1) l1[i] = j + 1;
if (a[j] <= a[i] + 2 * x - 1) l2[i] = j + 1;
}
}
int s1 = min(n, r), s2 = min(n, g);
f[0][0] = 1;
for (int i = 0; i <= s1; ++i) {
for (int j = 0; j <= s2; ++j) {
if (i == 0 && j > 0) f[i][j] = l2[f[i][j - 1]];
if (j == 0 && i > 0) f[i][j] = l1[f[i - 1][j]];
if (i > 0 && j > 0) f[i][j] = max(l2[f[i][j - 1]], l1[f[i - 1][j]]);
if (f[i][j] > n) return true;
}
}
return false;
}

int main() {
read(n), read(r), read(g);
for (int i = 1; i <= n; ++i) read(a[i]);
std::sort(a + 1, a + n + 1);
int L = 1, R = a[n] + 1;
while (L <= R) {
int mid = (L + R) >> 1;
if (check(mid)) R = mid - 1;
else L = mid + 1;
}
printf("%d\n", L);
return 0;
}


T3 迷宫

啊,次短路。

这里只说一下做法,如果有大佬能 hack 或者证明正确性欢迎联系。

首先以 \(1\) 为源点跑一遍最短路记为 ​​dis1[i]​​,以 \(n\) 为源点跑一遍最短路记为 ​​dis2[i]​​。

然后讨论三种情况:

1.次短路上的每条边都只走了一遍,这种情况枚举点 \(u\),再枚举连接 \(u\) 的一条边,假设连接了 \((u,v)\),这条路径长为 ​​dis1[u] + w + dis2[v]​

2.次短路上有一条边走了两遍,这种情况枚举点 \(u\),再枚举连接 \(u\) 的一条边走两遍,这条路径长为 ​​dis1[u] + w * 2 + dis2[u]​

3.次短路上有一条边走了三遍,这种情况枚举边走三遍,假设连接了 \((u,v)\),要考虑是 \(1\rightarrow u\) 还是 \(1\rightarrow v\)两种情况,路径长为 ​​dis1[u] + w * 3 + dis2[v]​​ 以及 ​​dis1[v] + w * 3 + dis2[u]​​。

在上述的所有情况中取大于最短路的最小值。

Code:

#include <cstdio>
#include <cstring>
#include <string>
#include <iostream>
#include <algorithm>
#define M 100001
#define inf 1061109567

typedef long long ll;
int min(int a, int b) { return a < b ? a : b; }
int max(int a, int b) { return a > b ? a : b; }
inline void read(int &T) {
int x = 0; bool f = 0; char c = getchar();
while (c < '0' || c > '9') { if (c == '-') f = !f; c = getchar(); }
while (c >= '0' && c <= '9') { x = x * 10 + c - '0'; c = getchar(); }
T = f ? -x : x;
}

bool vis[M];
int n, m, cnt, head[M], dis1[M], dis2[M];
struct Edge {
int u, v, w, nxt;
}e[M << 1];

void add(int u, int v, int w) {
e[++cnt].u = u, e[cnt].v = v, e[cnt].w = w;
e[cnt].nxt = head[u], head[u] = cnt;
}

void Dijkstra(int t) {
memset(vis, 0, sizeof vis);
if (t == 1) {
memset(dis1, 0x3f3f3f, sizeof dis1);
dis1[1] = 0;
for (int i = 1; i <= n; ++i) {
int k = 0;
for (int j = 1; j <= n; ++j) {
if (!vis[j] && dis1[j] < dis1[k]) k = j;
}
vis[k] = true;
for (int j = head[k]; j; j = e[j].nxt) {
if (!vis[e[j].v] && dis1[e[j].v] > dis1[k] + e[j].w) {
dis1[e[j].v] = dis1[k] + e[j].w;
}
}
}
} else {
memset(dis2, 0x3f3f3f, sizeof dis2);
dis2[n] = 0;
for (int i = 1; i <= n; ++i) {
int k = 0;
for (int j = 1; j <= n; ++j) {
if (!vis[j] && dis2[j] < dis2[k]) k = j;
}
vis[k] = true;
for (int j = head[k]; j; j = e[j].nxt) {
if (!vis[e[j].v] && dis2[e[j].v] > dis2[k] + e[j].w) {
dis2[e[j].v] = dis2[k] + e[j].w;
}
}
}
}
}

int main() {
read(n), read(m);
for (int i = 1, u, v, w; i <= m; ++i) {
read(u), read(v), read(w);
add(u, v, w), add(v, u, w);
}
Dijkstra(1), Dijkstra(n);
int ans = inf;
for (int i = 1; i <= n; ++i) {
int minn = inf;
for (int j = head[i]; j; j = e[j].nxt) {
if (dis1[i] + e[j].w + dis2[e[j].v] != dis1[n]) ans = min(ans, dis1[i] + e[j].w + dis2[e[j].v]);
minn = min(minn, e[j].w);
}
ans = min(ans, minn * 2 + dis1[i] + dis2[i]);
}
for (int i = 1; i <= m; ++i) {
ans = min(ans, e[i].w * 3 + dis1[e[i].u] + dis2[e[i].v]);
ans = min(ans, e[i].w * 3 + dis2[e[i].u] + dis1[e[i].v]);
}
std::cout << ans << '\n';
return 0;
}


After

发现自己对之前做过的题都没啥印象了,之前能切的题不会了,之前不会的题乱搞搞出来了,挺怪的,好像我把忘记技能返还的点数点到了乱搞上一样。