LightOJ 1074

<题目链接>

题目大意:
有n个城市,每一个城市有一个拥挤度$A_i$,从一个城市I到另一个城市J的时间为:$(A_v-A_u)^3$。问从第一个城市到达第$k$个城市所花的时间,如果不能到达,或者时间小于3输出?否则输出所花的时间。

解题分析:

很明显,此题路段的权值可能为负,所以我们就不能用Dijkstra算法求最短路了。需要注意的是,当点存在负环的时候,就要将负环所能够到达的所有点全部标记,从起点到这些点的最短路是不存在的(因为假设如果存在最短路,那么只要途中在负环上多走几遍,那么重新算得的时间一定会变少,所以不存在最短路)。所以,总的来说,对于本题,对那些负环能够到达的点,和从起点无法到达的点,和时间小于3的点,全部输出“?”,其他满足条件的直接输出最短时间就行。

#include <bits/stdc++.h>
using namespace std;
const int N = 220,INF = 0x3f3f3f3f;

struct Edge { int to,nxt,val; } edge[N * N];

int res, n;
int h[N];
bool vis[N];                      //记录该点是否在队列内
bool cir[N];                      //记录该点是否为负环上的点
int a[N], dist[N], cnt[N];      // cnt[]数组记录该数在队列中出现的次数

void dfs(int u) {  //将该负环所能够达到的所有点全部标记
  cir[u] = true;
  for (int i = h[u]; i != -1; i = edge[i].nxt) {
    int v = edge[i].to;
    if (!cir[v]) dfs(v);
  }
}
void init() {
  memset(h, -1, sizeof(h));
  res = 0;
}
void add(int u, int v, int w) {
  edge[res]=Edge{ v,h[u],w };h[u]=res++;
}
void SPFA(int st) {
  memset(vis, false, sizeof(vis));
  memset(cir, false, sizeof(cir));
  memset(cnt, 0, sizeof(cnt));
  for (int i = 1; i <= n; i++) dist[i] = INF;
  dist[st] = 0;
  queue<int> q;q.push(st);
  cnt[st] = 1;vis[st] = true;
  while (!q.empty()) {
    int u = q.front(); q.pop();
    vis[u] = false;  //当该点从队列中pop掉之后,就要清除vis标记
    for (int i = h[u]; i != -1; i = edge[i].nxt) {
      int v = edge[i].to;
      if (cir[v]) continue;  //如果是负环上的点
      if (dist[v] > dist[u] + edge[i].val) {
        dist[v] = dist[u] + edge[i].val;
        if (!vis[v]) {  //如果该点不在队列中
          vis[v] = true;
          q.push(v);
          cnt[v]++;
          if (cnt[v] > n) dfs(v);  //若存在负环,就用dfs将该负环能够达到的所有点标记
        }
      }
    }
  }
}
int main() {
  int t; scanf("%d", &t);
  int ncase = 0;
  while (t--) {
    init();
    scanf("%d", &n);
    for (int i = 1; i <= n; i++) scanf("%d", &a[i]);
    int m; scanf("%d", &m);
    while (m--) {
      int u, v; scanf("%d %d", &u, &v);
      add(u, v, (a[v] - a[u]) * (a[v] - a[u]) * (a[v] - a[u]));
    }
    SPFA(1);
    printf("Case %d:
", ++ncase);
    scanf("%d", &m);
    while (m--) {
      int u;scanf("%d", &u);
      if (cir[u] || dist[u] < 3 || dist[u] ==INF)  //如果询问的点能由负环达到、或者到起点的最小受益小于3、或者询问的点不可达
        puts("?");
      else printf("%d
", dist[u]);
    }
  }
}

本题直接标记能够被负环上的点松弛>n次的点(包括负环上的点)也能过。

但是个人认为这种做法是不够严谨的,因为我的松弛是有终止条件的(因为没有必要,但是实际的问题时是能够在负环上一直转的),即,如果被松弛大于n次,就不再松弛,所以那些会被负环到达的点可能不会被松弛大于n次,比如它还能被别的路松弛,使得它到起点的距离非常短,这样负环上的点转了几圈之后才会小于它之前松弛的到起点的最短路,这样可能使得这个点进入队列的次数<n次(因为负环上的点我设置的是>n次自动终止)。

#include <bits/stdc++.h>
using namespace std;
const int N = 220, inf = 0x3f3f3f3f;

struct Edge { int to,nxt,val; } edge[N * N];

bool vis[N], cir[N];                    
int n, h[N], res, a[N], dist[N], cnt[N];        //cnt[]数组记录该数在队列中出现的次数

void init() {
  memset(h, -1, sizeof(h));
  res = 0;
}
void add(int u, int v, int w) {
  edge[res]=Edge{ v,h[u],w };h[u]=res++;
}
void SPFA(int st) {
  memset(vis, false, sizeof(vis));
  memset(cir, false, sizeof(cir));
  memset(cnt, 0, sizeof(cnt));
  for (int i = 1; i <= n; i++) dist[i] = inf;
  dist[st] = 0;
  queue<int> q;q.push(st);
  cnt[st] = 1;vis[st] = true;
  while (!q.empty()) {
    int u = q.front(); q.pop();
    vis[u] = false; 
    for (int i = h[u]; i != -1; i = edge[i].nxt) {
      int v = edge[i].to;
      if (cir[v]) continue;  
      if (dist[v] > dist[u] + edge[i].val) {
        dist[v] = dist[u] + edge[i].val;
        if (!vis[v]) {  
          vis[v] = true;
          q.push(v);
          cnt[v]++;
          if(cnt[v]>n) cir[v]=true;          //本题直接标记负环中的点以及能够被负环中的点松弛>n次的点也能过     
        }
      }
    }
  }
}
int main() {
  int t; scanf("%d", &t);
  int ncase = 0;
  while (t--) {
    init();
    scanf("%d", &n);
    for (int i = 1; i <= n; i++) scanf("%d", &a[i]);
    int m; scanf("%d", &m);
    while (m--) {
      int u, v; scanf("%d %d", &u, &v);
      add(u, v, (a[v] - a[u]) * (a[v] - a[u]) * (a[v] - a[u]));
    }
    SPFA(1);
    printf("Case %d:
", ++ncase);
    scanf("%d", &m);
    while (m--) {
      int u;scanf("%d", &u);
      if (cir[u] || dist[u] < 3 || dist[u] ==inf) puts("?");
      else printf("%d
", dist[u]);
    }
  }
}
View Code

类似于评论区里对最短距离的判断也能过(这与上面这个问题类似,但是也有一点区别)。

#include <bits/stdc++.h>
using namespace std;
const int N = 220, inf = 0x3f3f3f3f;

struct Edge { int to,nxt,val; } edge[N * N];

bool vis[N], cir[N];                    
int n, h[N], res, a[N], dist[N], cnt[N];       

void init() {
  memset(h, -1, sizeof(h));
  res = 0;
}
void add(int u, int v, int w) {
  edge[res]=Edge{ v,h[u],w };h[u]=res++;
}
void SPFA(int st) {
  memset(vis, false, sizeof(vis));
  memset(cir, false, sizeof(cir));
  memset(cnt, 0, sizeof(cnt));
  for (int i = 1; i <= n; i++) dist[i] = inf;
  dist[st] = 0;
  queue<int> q;q.push(st);
  cnt[st] = 1;vis[st] = true;
  while (!q.empty()) {
    int u = q.front(); q.pop();
    vis[u] = false; 
    for (int i = h[u]; i != -1; i = edge[i].nxt) {
      int v = edge[i].to;
      if (cir[v]) continue;  
      if (dist[v] > dist[u] + edge[i].val) {
        dist[v] = dist[u] + edge[i].val;
        if (!vis[v]) {  
          vis[v] = true;
          q.push(v);
          cnt[v]++;
          if(cnt[v]>n) cir[v]=true;         
        }
      }
    }
  }
}
int main() {
  int t; scanf("%d", &t);
  int ncase = 0;
  while (t--) {
    init();
    scanf("%d", &n);
    for (int i = 1; i <= n; i++) scanf("%d", &a[i]);
    int m; scanf("%d", &m);
    while (m--) {
      int u, v; scanf("%d %d", &u, &v);
      add(u, v, (a[v] - a[u]) * (a[v] - a[u]) * (a[v] - a[u]));
    }
    SPFA(1);
    printf("Case %d:
", ++ncase);
    scanf("%d", &m);
    while (m--) {
      int u;scanf("%d", &u);
      if (dist[u] < 3 || dist[u] == inf) puts("?");
      else printf("%d
", dist[u]);
    }
  }
}
View Code
原文地址:https://www.cnblogs.com/00isok/p/9568773.html