• 「赛后总结」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
    ", 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
    ", 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 ightarrow u) 还是 (1 ightarrow 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 << '
    ';
      return 0;
    }
    

    After

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

  • 相关阅读:
    shipyard-----------docker容器的可视化管理
    dockerfile制作镜像
    docker容器ip地址的设定
    docker镜像文件的安装和容器的操作
    Docker网络模式
    docker整体了解
    spring的aop学习(1)
    SqlSessionFactory的创建过程
    spring+springMVC+Maven+mysql环境搭建
    dubbo入门示例
  • 原文地址:https://www.cnblogs.com/poi-bolg-poi/p/15427233.html
Copyright © 2020-2023  润新知