「赛后总结」20211019(重拾20200906)

写在前面

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

这次主要是补充一下 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

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

posted @ 2021-10-20 08:32  yu__xuan  阅读(57)  评论(2编辑  收藏  举报