天天看点

「SDOI2010」大陆争霸

知识点:最短路

原题面: Luogu

讲课的时候随便找的题,一眼了,瞎写了一下挂了。

看了题解,学习了。

题意简述

给定一 \(n\) 个点,\(m\) 条边的有向图,边有边权。

有无限个人同时从 \(1\) 号节点出发,可按照任意路径行走。

对于每一个节点,存在一些限制节点,当所有的限制节点都有人进入后,人才可进入该节点。

求在上述限制下,\(1\rightarrow n\) 的最短路。

\(1\le n\le 3\times 10^3\),\(1\le m\le 7\times 10^4\)。

分析题意

带有限制的最短路问题。

设 \(arrive_i\) 表示在满足上述限制下,\(1\rightarrow i\) 的最短路。

\(in_i\) 表示节点 \(i\) 的所有限制被摧毁的最短时间。

\(dis_i\) 表示进入 \(i\) 的实际时间。

则显然有:

\[in_i = \max\limits_{j \text{为} i \text{的限制节点}}{dis_j}_{}

\]

\[dis_i = \max{(arrive_i, in_i)}

答案即为 \(dis_n\)。

发现一个点能用于更新其他点,前提是该点能被进入,即所有该点的限制节点均可到达,且该点的所有前驱均可到达。

考虑使用 Dijkstra 求得上述 \(arrive\) 与 \(in\)。

每次找到还未被标记的,所有限制节点均已被标记的,\(dis\) 最小的节点,将其标记。

可以证明不存在其他节点可以疏导该节点,则用它来疏导与其相连的节点的 \(arrive\),并更新以其为限制节点的节点的 \(in\)。

数据范围较小,不进行优化也可过,代码中使用了堆优化。

以下是一个容易想到,但是错误的拓扑排序做法。

先跑出以 \(1\) 为起点的最短路。

由于保证有解,发现节点的限制关系,形成了一张 DAG。

对这张 DAG 进行拓扑排序,将 没有入度的点 的 \(dis_i\) 赋值为最短路,之后使用上述转移进行更新。

获得了 10 分的好成绩,以下是一组 hack 数据:

4 3
1 2 1
1 3 5
2 4 1
0
0
1 2
0
           

如果按上述方法进行,会输出 2,原因是终点并没有被直接限制,在 DAG 中没有入度,会被直接赋值。

爆零小技巧

多造数据卡自己一定没有错。

代码实现

//知识点:最短路 
/*
By:Luckyblock
*/
#include <algorithm>
#include <cctype>
#include <cmath>
#include <cstdio>
#include <cstring>
#include <queue>
#include <vector>
#define ll long long
#define pr std::pair
#define mp std::make_pair
const int kMaxn = 3e3 + 10;
const int kMaxm = 1e5 + 10;
const ll kInf = 1e15 + 2077;
//=============================================================
int n, m, s;
int edge_num, head[kMaxn], v[kMaxm], w[kMaxm], ne[kMaxm];
ll arrive[kMaxn], in[kMaxn], dis[kMaxn];
bool vis[kMaxn];
int into[kMaxn];
std :: vector <int> pro[kMaxn];
//=============================================================
inline int read() {
  int f = 1, w = 0;
  char ch = getchar();
  for (; !isdigit(ch); ch = getchar())
    if (ch == '-') f = -1;
  for (; isdigit(ch); ch = getchar()) w = (w << 3) + (w << 1) + (ch ^ '0');
  return f * w;
}
void Chkmax(ll &fir_, ll sec_) {
  if (sec_ > fir_) fir_ = sec_;
}
void Chkmin(int &fir_, int sec_) {
  if (sec_ < fir_) fir_ = sec_;
}
void AddEdge(int u_, int v_, int w_) {
  v[++ edge_num] = v_, w[edge_num] = w_;
  ne[edge_num] = head[u_], head[u_] = edge_num;
}
void Dijkstra(int s_) {
  std :: priority_queue <pr <ll, int> > q;
  memset(vis, false, sizeof (vis));
  memset(arrive, 63, sizeof (arrive));
  memset(arrive, 63, sizeof (dis));
  arrive[s_] = 0;
  dis[s_] = 0;
  q.push(mp(0, s_));
  
  while (! q.empty()) {
    int u_ = q.top().second;
    q.pop();
    if (vis[u_]) continue ;
    vis[u_] = true;
    
    for (int i = head[u_]; i; i = ne[i]) {
      int v_ = v[i], w_ = w[i];
      if (arrive[v_] > dis[u_] + 1ll * w_) {
        arrive[v_] = dis[u_] + 1ll * w_;
        dis[v_] = std :: max(arrive[v_], in[v_]);
        if (! into[v_]) q.push(mp(- dis[v_], v_));
      }
    }
    for (int j = 0, size = pro[u_].size(); j < size; ++ j) {
      int v_ = pro[u_][j];
      into[v_] --;
      in[v_] = std :: max(in[v_], dis[u_]);
      dis[v_] = std :: max(arrive[v_], in[v_]);
      if (! into[v_]) {
        q.push(mp(- dis[v_], v_));
      }
    }
  }
}
//=============================================================
int main() {
  n = read(), m = read();
  for (int i = 1; i <= m; ++ i) {
    int u_ = read(), v_ = read(), w_ = read();
    AddEdge(u_, v_, w_);
  }
  for (int i = 1; i <= n; ++ i) {
    int l = read();
    for (int j = 1; j <= l; ++ j) {
      int u_ = read();
      pro[u_].push_back(i);
      into[i] ++;
    }
  }
  Dijkstra(1);
  printf("%lld\n", dis[n]);
  return 0;
}
           

作者@Luckyblock,转载请声明出处。