首页 > 其他分享 >4 月 27 日测试题解

4 月 27 日测试题解

时间:2023-04-29 20:00:12浏览次数:34  
标签:std 27 测试题 int edges le vector dis

4 月 27 日测试题解

最短路专场

T1 \({\color{green}{\text{100pts}}}\text{/100pts}\)

题意

给出 \(m\) 个变量与 \(n\) 个约束,每个约束形如以下三种中的一种;

  • \(x_i - x_j \le w\)
  • \(x_i - x_j \ge w\)
  • \(x_i - x_j = w\)

有 \(q\) 个询问,每个询问为形如 \((x_i, x_j)\) 的二元组,要求 \(\max(x_i - x_j)\) 的值,当可以取到正无穷时,输出 \(\texttt{Infinity}\)。

对于 \(30\%\) 的数据,\(n, m \le 5\);
对于 \(100\%\) 的数据,\(m \le 200\),\(n \le 10^4\),\(q \le 10^6\),\(|w| \le 10^4\)。

思路

\(\text{100pts}\)

这道题是差分约束的裸题。对于每个约束,我们通过手段将其归约到同一种类型,即形如 \(x \le y + C\) 的形式,然后联想到 SSSP 中松弛操作的数学基础:三角不等式。对于一条边 \(e = (u, v, w)\),我们有 \(dis_v \le dis_u + w\)。

我们可以考虑建出图,对于每个形如上边一般形式的约束,建一条 \(y \to x\),权值为 \(C\) 的边,然后在上边跑最短路求出一组合法解。但此时,由于我们求的是最短路,得到的解事实上是满足约束条件的最大的一组解,于是满足了题目中的所有条件。

对于题目中的三种约束,我们有:

  • \(x_i - x_j \le w \Leftrightarrow x_i \le x_j + w\)
  • \(x_i - x_j \ge w \Leftrightarrow x_j \le x_i + (-w)\)
  • \(x_i - x_j = w \Leftrightarrow x_i \le x_j + w, x_j \le x_i + (-w)\)

题目中保证给出的约束条件不会相互冲突,于是出现可以取 \(+\infty\) 的情况只可能是题目中的第二种约束或两变量间没有约束的情况,特判一下即可。

可以添加一个超级源来跑,不过这道题要求的值较为特殊,对于一组询问 \((x_i, x_j)\),其答案应为以 \(x_j\) 为源点的 \(dis_{x_i}\) 值。

代码

\(\text{100pts}\)

#include <bits/stdc++.h>

using i64 = long long;

struct Graph {
  static constexpr i64 INF = 1e18;
  struct Edge {
    int u, v;
    i64 w;
    Edge(int _u, int _v, i64 _w) : u(_u), v(_v), w(_w) {}
  };
  int n;
  std::vector<Edge> edges;
  std::vector<std::vector<int>> e;
  
  Graph(int _n) : n(_n + 1) {
    e.resize(n);
    return;
  }
  
  void insert(int u, int v, int w) {
    edges.emplace_back(u, v, w);
    e[u].push_back(edges.size() - 1);
    return;
  }
  
  void add(int u, int v, int w) {
    insert(u, v, w), insert(v, u, w);
    return;
  }
  
  std::vector<i64> spfa(int s) {
    std::vector<i64> dis(n, INF);
    std::vector<bool> vis(n, false);
    std::queue<int> q;
    
    dis[s] = 0;
    q.push(s);
    while (!q.empty()) {
      int u = q.front();
      vis[u] = false;
      q.pop();
      for (auto i : e[u]) {
        int v = edges[i].v, w = edges[i].w;
        if (dis[v] > dis[u] + w) {
          dis[v] = dis[u] + w;
          if (!vis[v]) {
            vis[v] = true, q.push(v);
          }
        }
      }
    }
    
    return dis;
  }
};

int main() {
  std::ios::sync_with_stdio(false);
  std::cin.tie(0);
  
  constexpr i64 INF = 1e18;
 
  int n, m;
  std::cin >> n >> m;
  
  Graph g(n);
  for (int i = 0; i < m; i++) {
    int opt, u, v, w;
    std::cin >> opt >> u >> v >> w;
    if (opt == 0) {
      g.insert(v, u, w);
    } else if (opt == 1) {
      g.insert(u, v, -w);
    } else {
      g.insert(v, u, w);
      g.insert(u, v, -w);
    }
  }
  
  int q;
  std::cin >> q;
  
  std::vector<std::vector<i64>> dis(n + 1);
  for (int i = 1; i <= n; i++) {
    dis[i] = g.spfa(i);
  }
  
  while (q--) {
    int u, v;
    std::cin >> u >> v;
    if (dis[v][u] == INF) {
      std::cout << "Infinity\n";
    } else {
      std::cout << dis[v][u] << "\n";
    }
  }
  
  return 0;
}

T2 \({\color{green}{\text{100pts}}}\text{/100pts}\)

题意

有连接 \(n\) 个路口的 \(m\) 条双向公路,每个路口有一盏红绿灯,红绿灯各自的时长由 \(r\) 与 \(g\) 两个序列分别给出,你可以在任意时间到达一个路口,但只有当前路口为绿灯时,你才可以离开。

给出源点和汇点,求从源到汇的最短时间。

对于 \(30\%\) 的数据,\(n \le 10\),\(m \le 20\);
对于 \(100\%\) 的数据,\(n \le 2.5 \times 10^5\),\(m \le 5 \times 10^5\),\(r_i, g_i \le 10^4\),\(w \le 10^6\)。

思路

\(\text{100pts}\)

这道题就是普通 SSSP 问题的一个变体。

在松弛时,考虑当前松弛的节点处于红灯还是绿灯。如果是红灯的话,就补上一个等待时间即可。其他的就跟普通的 SSSP 一样了。

当然,你也可以拆点跑分层图,这样就不用讨论了。

这图的规模比较大,最好使用 Dijkstra,你要是能用 SPFA 卡过去也行。

代码

\(\text{100pts}\)

#include <bits/stdc++.h>

using i64 = long long;

namespace fastIO {
template<typename T>
void read(T &x) {
  x = 0;
  T f = 1;
  char c = getchar();
  while (!std::isdigit(c)) {
    if (c == '-') { f = -1; }
    c = getchar();
  }
  while (std::isdigit(c)) {
    x = 10 * x + c - '0';
    c = getchar();
  }
  x *= f;
  return;
}

template<typename T, typename ...arg>
void read(T &x, arg &...args) {
  read(x), read(args...);
  return;
}
}

struct Graph {
  struct Edge {
    int u, v;
    i64 w;
    Edge(int _u, int _v, i64 _w) : u(_u), v(_v), w(_w) {}
  };
  int n;
  std::vector<Edge> edges;
  std::vector<int> r, g;
  std::vector<std::vector<int>> e;
  
  Graph(int _n) : n(_n + 1) {
    e.resize(n);
    r.resize(n), g.resize(n);
    return;
  }
  
  void insert(int u, int v, int w) {
    edges.emplace_back(u, v, w);
    e[u].push_back(edges.size() - 1);
    return;
  }
  
  void add(int u, int v, int w) {
    insert(u, v, w), insert(v, u, w);
    return;
  }
  
  std::vector<i64> dijkstra(int s, int t) {
    std::vector<i64> dis(n, 1e18);
    std::vector<bool> vis(n, false);
    std::priority_queue<
      std::pair<int, int>,
      std::vector<std::pair<int, int>>,
      std::greater<std::pair<int, int>>
    > pq;
    
    dis[s] = 0, pq.emplace(0, s);
    
    while (!pq.empty()) {
      int u = pq.top().second;
      pq.pop();
      if (vis[u]) {
        continue;
      }
      vis[u] = true;
      for (auto i : e[u]) {
        int v = edges[i].v;
        i64 w = edges[i].w, cur = dis[u] + w;
        
        if (v != t && r[v] + g[v] && cur % (r[v] + g[v]) <= r[v]) {
          cur += (r[v] - cur % (r[v] + g[v]));  
        }
        
        if (dis[v] > cur) {
          dis[v] = cur;
          pq.emplace(dis[v], v);
        }
      }
    }
    
    return dis;
  }
};

int main() {
  int n, m, s, t;
  fastIO::read(n, m, s, t);
  
  Graph g(n);
  for (int i = 1; i <= n; i++) {
    fastIO::read(g.r[i], g.g[i]);
  }
  
  for (int i = 0; i < m; i++) {
    int u, v;
    i64 w;
    fastIO::read(u, v, w);
    g.add(u, v, w);
  }
  
  g.add(0, s, 0);
  auto dis = g.dijkstra(0, t);
  
  printf("%lld\n", dis[t]);
  
  return 0;
}

T3 \({\color{orange}{\text{80pts}}}\text{/100pts}\)

题意

给出一张 DAG,每个点的出边权值相同,你可以任意改变任意一个点所有出边的权值,还需要回答以下问题:

  1. 该图的最长路;
  2. 能影响该图最长路的点的个数;
  3. 能影响该图最长路的所有点。

有三问,但是没有 SPJ,全部回答正确才得分。

\(n \le 10^5\)。

思路

\(\text{50pts}\)

这部分数据保证 \(n \le 1000\)。

求最长路是简单的,用拓扑跑一遍即可,我们重在解决第二三问。

这部分的数据范围在暗示你写 \(O(n^2)\) 的算法,于是你考虑枚举每个点,减小它出边的权值,然后再跑一遍拓扑来检验最长路是否有变短即可。若令 \(n\) 与 \(m\) 同级,则时间复杂度为 \(O(n^2)\)。

\(\text{100pts}\)

最长路的求法当然不变不然你给我写个更优的出来

不难看出,第二问实际上求的是所有最长路都经过的点,证明是真的很显然,所以就不给了。

于是你把所有最长路上的店与边抠出来塞到一个新图里,当然这新图也是个 DAG,然后再跑 BFS 求出每条路径经过每个点的次数,若一个点被标记过的次数等于最长路数量,那它就是关键的。

当然也可以把它抠出来改成一个无向图跑割点,本质是一样的。

时间复杂度 \(O(n + m)\) 带超大常数。

代码

\(\text{100pts}\)

/**
 * @file    
 * @author  ForgotDream
 * @brief   
 * @date    2023-04-28
 */
#include <bits/stdc++.h>

using i64 = long long;
using i64u = unsigned long long;
using f80 = long double;

namespace Helper {
void useFileInuput() {
  #ifndef ONLINE_JUDGE
  freopen("tmp.in", "r", stdin);
  freopen("tmp.out", "w", stdout);
  #endif
  return;
}

void debug(const std::string &s) {
  std::clog << s << "\n";
  return;
}
}

struct Graph {
  static constexpr int INF = 1e9;
  struct Edge {
    int u, v, w;
    Edge(int _u = 0, int _v = 0, int _w = 0) : u(_u), v(_v), w(_w) {}
  };
  int n;
  std::vector<Edge> edges;
  std::vector<std::vector<int>> e;
  std::vector<int> in;

  Graph(int _n) : n(_n) {
    e.resize(n);
    in.resize(n);
    return;
  }

  void add(int u, int v, int w) {
    edges.emplace_back(u, v, w);
    e[u].push_back(edges.size() - 1);
    return;
  }

  std::vector<int> topoSort() {
    std::vector<int> dis(n, -INF);
    std::queue<int> q;

    q.push(0), dis[0] = 0;
    while (!q.empty()) {
      int u = q.front();
      q.pop();
      for (auto i : e[u]) {
        int v = edges[i].v, w = edges[i].w;
        if (!--in[v]) {
          q.push(v);
        }
        dis[v] = std::max(dis[v], dis[u] + w);
      }
    }

    return dis;
  }

  void addIn(int u) {
    in[u]++;
    return;
  }

  std::vector<int> solve() {
    std::vector<int> res, dfn(n), low(n);
    int clk = 0;

    std::function<void(int, int)> tarjan = [&](int u, int rt) {
      low[u] = dfn[u] = ++clk;
      int child = 0;

      for (auto i : e[u]) {
        int v = edges[i].v;
        if (!dfn[v]) {
          tarjan(v, rt);
          low[u] = std::min(low[u], low[v]);
          child += (u == rt);
          if (low[v] >= dfn[u] && u != rt) {
            res.push_back(u);
          }
        } else {
          low[u] = std::min(low[u], dfn[v]);
        }
      }

      if (child >= 2 && u == rt) {
        res.push_back(u);
      }

      return;
    };

    tarjan(0, 0);

    return res;
  }
  
};

signed main() {
  std::ios::sync_with_stdio(false);
  std::cin.tie(nullptr);

  int n;
  std::cin >> n;

  Graph g(n + 2), rev(n + 2);
  // rev is the reverse graph of the original graph.
  // it's used to build another graph which only contains the special vertexes and edges
  // on the longest path of the original graph.
  std::vector to(n + 2, std::vector<int>());
  std::vector<int> t(n + 1);
  for (int i = 1; i <= n; i++) {
    int c;
    std::cin >> t[i] >> c;
    if (c == 0) {
      g.add(0, i, 0), rev.add(i, 0, 0);
      g.addIn(i);
    } else {
      while (c--) {
        int v;
        std::cin >> v;
        to[v].push_back(i);
        g.addIn(i);
      }
    }
    g.add(i, n + 1, t[i]), rev.add(n + 1, i, t[i]);
    g.addIn(n + 1);
  }

  for (int i = 1; i <= n; i++) {
    for (auto j : to[i]) {
      g.add(i, j, t[i]), rev.add(j, i, t[i]);
    }
  }

  auto dis = g.topoSort();

  Graph rst(n + 2);  // the graph mentioned above.
  std::queue<int> q;

  q.push(n + 1);
  while (!q.empty()) {
    int u = q.front();
    q.pop();
    for (auto i : rev.e[u]) {
      int v = rev.edges[i].v, w = rev.edges[i].w;
      if (dis[u] == dis[v] + w) {
        rst.add(u, v, w), rst.add(v, u, w);
        q.push(v);
      }
    }
  }

  auto ans = rst.solve();
  std::sort(ans.begin(), ans.end());

  std::cout << dis[n + 1] << "\n";
  std::cout << ans.size() << "\n";
  for (int i = 0; i < ans.size(); i++) {
    std::cout << ans[i] << " \n"[i == ans.size() - 1];
  }

  return 0;
}

Epilogue

我为什么只得了 80 呢?因为我是直接记录了每个点的前驱,而没有考虑到一个点可能有多个前驱,但是运气还是比较好,混到了 80。

标签:std,27,测试题,int,edges,le,vector,dis
From: https://www.cnblogs.com/forgot-dream/p/17364422.html

相关文章

  • 4 月 21 日测试题解
    4月21日测试题解T1\({\color{green}{\text{100pts}}}\text{/100pts}\)题意给出平面上的两条线段,求线段之间的距离。\(\text{|线段端点坐标|}\le10^4\)。思路一开始想的是分讨,但是又怕自己写挂了,所以就写了三分套三分。至少这个不怕少讨论一个情况。既然是三分套三分,......
  • 面试经验4-27
    一、解释一下为什么发生tcp的粘包现象,以及怎么解决?tcp为了节约资源采用的是流式传输。接收端一下接收了多个包,粘在了一起。多个包首尾相接,无法区分是哪个包。原因:发送方等发送缓冲区满才发送,接收缓冲区等满了才接受,多个包合成一个发送。解决方法:不允许发送缓冲区满才发,提高优先......
  • KubeSphere 社区双周报 | 杭州站 Meetup 议题征集中 | 2023.04.14-04.27
    KubeSphere社区双周报主要整理展示新增的贡献者名单和证书、新增的讲师证书以及两周内提交过commit的贡献者,并对近期重要的PR进行解析,同时还包含了线上/线下活动和布道推广等一系列社区动态。本次双周报涵盖时间为:2023.04.14-2023.04.27。贡献者名单新晋KubeSphereCon......
  • 2023-4-27 #52 来看这万千旧梦
    323AT_xmascon21_cCountMe先做没有问号的情况。把问题倒着做,每次删只能删连续段的末端\(0\),或是连续段的开头\(1\),若我们在开头插入\(0\),在结尾插入\(1\)并强制这些不能删,那么我们能将\(0\)连续段与\(1\)连续段匹配,操作可以看作将一个\(01\)变成\(0\)或是\(1\)......
  • hdoj 展开字符串 1274 (字符串递归) 好题
    展开字符串TimeLimit:2000/1000MS(Java/Others)   MemoryLimit:65536/32768K(Java/Others)TotalSubmission(s):2116   AcceptedSubmission(s):1017ProblemDescription在纺织CAD系统开发过程中,经常会遇到纱线排列的问题。该问题的描述是这样的......
  • 产品原型20-20230427
           ......
  • 4.27
    #include<iostream>usingnamespacestd;inti=1;voidother(){   staticinta=2;   staticintb;   intc=10;   a+=2;   i+=32;   c+=5;   cout<<"---OTHER---"<<endl;   cout<<"i:"......
  • 2023.4.27
    1//实验六任务22//定义猫科动物Animal类,由其派生出猫类(Cat)和豹类(Leopard),3//在Animal类中定义虚函数,输出“MynameisAnimal”,在派生类中4//分别重新定义该函数,显示“Mynameis**”,其中**为各自类名5#include<iostream>6#include<string>7usingnamespa......
  • 4.27
    问题描述:    小明有五本新书,要接给A、B、C这三位小朋友,若每人每次只能借一本,则可以有多少不同种的接法?设计思路1.根据题意,这五本数每个人都可借阅;2.则当第一个人挑选时可以有五种不同的选择,第二个人时有四种,最后一个人有三种;3.利用三次循环嵌套,对第一个人进行五次循......
  • C/C++会员管理系统[2023-04-27]
    C/C++会员管理系统[2023-04-27]综合设计实例四课题名称:会员管理系统I、题目的目的和要求(2-3人组)随着社会的进步,人们生活水平的提高,各种各样的会员应运而生。各种便民服务的地方为了提高服务粘性,留住顾客往往采用会员制,例如便利店、健身房,生鲜超市、美容美发店等等不一而足......