天天看點

「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,轉載請聲明出處。