1. 程式人生 > 實用技巧 >POJ-3662 Telephone Lines(分層圖最短路|二分)

POJ-3662 Telephone Lines(分層圖最短路|二分)

解法參考進階指南

題意:算1到N的路徑上把K條邊的邊權變為0後路徑中邊權的最大值最小。(無向圖中找一條1到N的路徑,使得K+1大的邊權儘量小。)

解法1:分層圖最短路。SPFA維護DP。

\(f[i][j]\)表示從1號點到\(i\)號點路徑上把\(j\)條邊變為0後最大邊權的最小值。

對於一條邊而言,要麼把他變為0,要麼不變為0。

變為0:\(f[v][j+1] = min(f[v][j+1], f[u][j+1])\)

不變: \(f[v][j] = min(f[v][j], max(f[u][j], w(u,v)))\)

轉移過程如果直接列舉\(j\)複雜度太高,因此使用SPFA維護這個DP。

最終的答案就是所有\(f[n][x](x<=k)\)的最小值

#include <cstdio>
#include <iostream>
#include <algorithm>
#include <cstring>
#include <vector>
#include <queue>

using namespace std;

int n, p, k, ans;

const int maxn = 1e4+10, N = 1e3+10;
struct E{int to, next, w;}e[maxn<<1];
int head[maxn], cnt;
void init() {
  memset(head,-1,sizeof head);
  cnt = 0;
}
void add(int u, int v, int w) {
  e[++cnt].to = v, e[cnt].w = w, e[cnt].next = head[u], head[u] = cnt;
}

struct node{
  node(){}
  node(int ID, int P) {
    id = ID, p = P;
  }
  int id, p;
};
int f[N][maxn]; //1號點到第i個點使用了j次免費路徑上的最大邊權
bool inq[N][maxn];
const int inf = 0x3f3f3f3f;
void spfa(int s) {
  memset(f,0x3f,sizeof f);
  f[s][0] = 0;
  queue<node> q;
  q.push(node(s,0));
  inq[s][0] = 1;
  while (!q.empty()) {
    int u = q.front().id, pp = q.front().p; q.pop();
    inq[u][pp] = 0;
    for (int i = head[u]; ~i; i = e[i].next) {
      int v = e[i].to, w = e[i].w;
      if (max(f[u][pp],w) < f[v][pp]) {
        f[v][pp] = max(f[u][pp],w);
        if (!inq[v][pp]) q.push(node(v,pp)), inq[v][pp] = 1;
      }
      if (pp+1<=k && f[u][pp] < f[v][pp+1]) {
        f[v][pp+1] = f[u][pp];
        if (!inq[v][pp+1]) q.push(node(v,pp+1)), inq[v][pp+1] = 1;
      }
    }
  }
}

int main() {
  init();
  scanf("%d%d%d",&n,&p,&k);
  for (int i = 1; i <= p; i++) {
    int u, v, w;
    scanf("%d%d%d",&u,&v,&w);
    add(u,v,w);
    add(v,u,w);
  }
  ans = inf;
  spfa(1);
  int ans = inf;
  for (int i = 0; i <= k; i++) ans = min(ans, f[n][i]);
  printf("%d\n",ans==inf? -1 : ans);
  return 0;
}

解法2:二分答案check最短路。

該解法應該更容易想到。每次二分重新構建一張圖,對於每次二分出的一個值\(x\),把邊權\(<=x\)的邊加入圖中,並把邊權設為0,把邊權\(>x\)的邊加入圖中,並把邊權設為1,這樣從1到N跑一遍最短路,代表把最短路的值的那麼多條邊變成0之後1到N的路徑上所有的值都\(<=x\)了,然後再check最短路和k哪個大即可。

這裡程式碼用dijkstra跑最短路(不會01BFS),速度還是挺快的。

#pragma GCC optimize(2)
#include <cstdio>
#include <iostream>
#include <algorithm>
#include <cstring>
#include <vector>
#include <queue>
#define pb push_back
#define mp make_pair
using namespace std;

int n, p, k;
const int inf = 0x3f3f3f3f;
const int maxn = 1010;
int dis[maxn];
bool vis[maxn];

vector<pair<pair<int, int>, int> > vec;
vector<pair<int, int > > e[maxn];

struct cmp{
  bool operator()(const pair<int,int> &x, const pair<int,int> &y) {
    return x.second > y.second;
  }
};

int dj(int s) {
  memset(vis,0,sizeof vis);
  memset(dis,0x3f,sizeof dis);
  priority_queue<pair<int,int>, vector<pair<int,int> > , cmp> q;
  q.push(mp(s,0));
  dis[s] = 0;
  while (!q.empty()) {
    int u = q.top().first, ww = q.top().second; q.pop();
    if (vis[u]) continue;
    vis[u] = 1;
    for (int i = 0; i < e[u].size(); i++) {
      int v = e[u][i].first, w = e[u][i].second;
      if (vis[v]) continue;
      if (dis[v] > dis[u] + w) {
        dis[v] = dis[u] + w;
        q.push(mp(v,dis[v]));
      }
    }
  }
  return dis[n];
} 

bool check(int x) {
  for (int i = 1; i <= n; i++) e[i].clear();
  for (int i = 0; i < vec.size(); i++) {
    if (vec[i].second <= x) {
      e[vec[i].first.first].pb(mp(vec[i].first.second,0));
      e[vec[i].first.second].pb(mp(vec[i].first.first,0));
    } else {
      e[vec[i].first.first].pb(mp(vec[i].first.second,1));
      e[vec[i].first.second].pb(mp(vec[i].first.first,1));
    }
  }
  return dj(1) <= k;
}

int main() {
  scanf("%d%d%d",&n,&p,&k);
  for (int i = 1; i <= p; i++) {
    int u, v, w;
    scanf("%d%d%d",&u,&v,&w);
    vec.pb(mp(mp(u,v),w));
  }
  int l = 0, r = inf;
  while (l < r) {
    int mid = l + r >> 1;
    if (check(mid)) r = mid;
    else l = mid + 1;
  }
  printf("%d\n",l==inf ? -1 : l);
  return 0;
}