挑战程序竞赛系列(11):2.5最短路径

挑战程序竞赛系列(11):2.5最短路径

详细代码可以fork下Github上leetcode项目,不定期更新。

练习题如下:

AOJ 0189: Convenient Location

思路:
更新任意两点之间的最短距离,采用松弛法,现成的算法有Floyd-Warshall算法,代码如下:

public class Main {

    static final int MAX_LOC = 10;

    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        String line;
        String[] words;

        while ((line = br.readLine()) != null && !line.isEmpty()) {

            int n = parseInt(line);
            if (n == 0) break;

            //init
            long[][] g = new long[MAX_LOC][MAX_LOC];
            int N = 0;

            for (int i = 0; i < g.length; i++) {
                for (int j = 0; j < g[i].length; j++) {
                    if (i != j) g[i][j] = Integer.MAX_VALUE;
                }
            }
            for (int i = 0; i < n; i++) {
                words = br.readLine().split(" ");
                int x, y, d;
                x = parseInt(words[0]);
                y = parseInt(words[1]);
                d = parseInt(words[2]);
                N = Math.max(N, Math.max(x, y));
                g[y][x] = g[x][y] = d;
            }

            //solve
            for (int k = 0; k <= N; k++) {
                for (int i = 0; i <= N; i++) {
                    for (int j = 0; j <= N; j++) {
                        g[i][j] = Math.min(g[i][j], g[i][k] + g[k][j]);
                    }
                }
            }

            int pos = Integer.MAX_VALUE;
            int min = Integer.MAX_VALUE;
            for (int i = 0; i <= N; i++) {
                int tmp = 0;
                for (int j = 0; j <= N; j++) {
                    tmp += g[i][j];
                }
                if (tmp < min) {
                    pos = i;
                    min = tmp;
                }
            }

            System.out.println(pos + " " + min);

        }
    }
}

POJ 2139: Six Degrees of Cowvin Bacon

和第一题一个思路,没什么好说的,代码如下:

static int INF = 1 << 29;

    public static void main(String[] args) throws IOException {
        Scanner in = new Scanner(System.in);
        int N = in.nextInt();
        int M = in.nextInt();
        int[][] g = new int[N][N];

        for (int i = 0; i < N; ++i) Arrays.fill(g[i],INF);
        for (int i = 0; i < N; ++i) g[i][i] = 0;

        for (int i = 0; i < M; ++i){
            int n = in.nextInt();
            int[] x = new int[n];
            for (int j = 0; j < n; ++j){
                x[j] = in.nextInt();
                x[j]--;
            }
            for (int k = 0; k < n; ++k){
                for (int l = k + 1; l < n; ++l){
                    g[x[k]][x[l]] = g[x[l]][x[k]] = 1;
                }
            }
        }

        for (int i = 0; i < N; ++i){
            for (int j = 0; j < N; ++j){
                for (int k = 0; k < N; ++k){
                    g[j][k] = Math.min(g[j][k],g[j][i] + g[i][k]);
                }
            }
        }

        int ans = INF;
        for (int i = 0; i < N; ++i){
            int sum = 0;
            for (int j = 0; j < N; ++j){
                sum += g[i][j];
            }
            ans = Math.min(ans, sum);
        }

        System.out.println(100 * ans / (N - 1));
    }

POJ 3268: Sliver Cow Party

求从某个顶点出发,到达目标顶点X之后再回到原地的所有最短路径中的最大值。

思路:(warShallFloyd算法)
它能求任意两点之间的最短距离,时间复杂度为 O(n3) 。代码如下:

static int INF = 1 << 29;
    static int[][] g;
    static int N;

    public static void main(String[] args) throws IOException {
        Scanner in = new Scanner(System.in);
        N = in.nextInt();

        int M = in.nextInt();
        int X = in.nextInt();
        g = new int[N][N];
        d = new int[N][N];
        for (int i = 0; i < N; ++i) Arrays.fill(g[i], INF);
        for (int i = 0; i < N; ++i) g[i][i] = 0;

        for (int i = 0; i < M; ++i){
            int f = in.nextInt();
            int t = in.nextInt();
            f--;
            t--;
            int c = in.nextInt();
            g[f][t] = c;
        }

        warshallFloyd();

        int max = 0;
        for (int i = 0; i < N; ++i){
            if (i == X) continue;
            max = Math.max(max, g[i][X] + g[X][i]);
        }
        System.out.println(max);
    }
    private static void warshallFloyd(){
        for (int i = 0; i < N; ++i){
            for (int j = 0; j < N; ++j){
                for (int k = 0; k < N; ++k){
                    g[j][k] = Math.min(g[j][k], g[j][i] + g[i][k]);
                }
            }
        }
    }

TLE了,顶点数1000,所以需要执行 10003 次,自然TLE,所以必须优化,采用DIJKSTRA算法,因为此题已经表明不存在负环,所以可以使用DIJKSTRA来计算任意两点之间的距离。

为什么有负环dijkstra算法就不适用了?其次,dijkstra算法为什么就比warshallFloyd算法快?之前困扰了我很久,今天能够解释了。关键问题在于负边是否存在。

Dijkstra:假设所有边都为正,不存在负环。

为什么需要这假设?这跟改进算法时间复杂度有关,在warshallFloyd算法中,每一轮操作都需要访问每个顶点,即时这个顶点已经被更新为最短路径,所以DIJKSTRA的一个优化思路是:

既然优先顶点已经知道了到源点s的最短距离是多少,它们在未来,到达该顶点的路径不需要再比较。

问题来了,该如何得知这个顶点已经是最短路径上的一个顶点了?DIJKSTRA把整个顶点集划分为【最短路径顶点集】和【未确定顶点集】,目标就是在每一轮松弛操作后,能够得到当前一个最短路径上的顶点。

有了这顶点有什么作用呢?因为源点到该顶点的路径一定是最短的,所以从该顶点出发连接未确定顶点集的路径中,必然会出现一条最短路径,指向一个新的顶点。

最重要的是,我们没必要更新其他顶点的连接边,因为它们还不是最短路径,更新无意义。而是仅更新从该顶点出发的所有邻接的顶点。

此处体现了算法的一个优化(更新了相邻顶点的边,而不是每轮所有边)。

证明:

源点到该顶点的路径一定是最短的,所以从该顶点出发连接未确定顶点集的路径中,必然会出现一条最短路径,指向一个新的顶点。

上述这结论需要证明一下,DIJKSTAR最重要的假设是不存在负边,还有一个重要的事实,不管松弛与否,更新必然导致d[i]递减,d[i]表示源点s到i的路径,所以d[i]是不可能递增的。

好了,现在经过第k轮,得到了d[i]是源点s到i的【最短路径】,我们选择方法是:

从前一轮最短路径的某个顶点出发,更新所有与之相连的顶点j,选择【未确定顶点集】中的d[j]最小的顶点为新的最短路径顶点。

注意是最小!这样就保证了更新的正确性,所以d[i]对我们来说,是所有其他d[j]的最小值,那么是否存在其他路径使得到d[i]的距离比现在还小呢?

没有,因为其他顶点d[j] > d[i],而任何从顶点j到顶点i的连接边都是正值,不可能有比d[i]小的路径存在,所以d[i]已经是最短路径中的一个顶点了。那么由此更新它的每一条边必然是安全的。(用到了最重要的不存在负边的假设)

代码如下:

static int INF = 1 << 29;
    static int[][] g;
    static int[][] d;
    static int N;

    public static void main(String[] args) throws IOException {
        Scanner in = new Scanner(System.in);
        N = in.nextInt();

        int M = in.nextInt();
        int X = in.nextInt();
        g = new int[N][N];
        d = new int[N][N];
        for (int i = 0; i < N; ++i) Arrays.fill(g[i], INF);
        for (int i = 0; i < N; ++i) g[i][i] = 0;

        for (int i = 0; i < M; ++i){
            int f = in.nextInt();
            int t = in.nextInt();
            f--;
            t--;
            int c = in.nextInt();
            g[f][t] = c;
        }

        for (int i = 0; i < N; ++i){
            //initial
            Arrays.fill(d[i], INF);
            d[i][i] = 0;
            dijkstra(i);
        }

        int max = 0;
        for (int i = 0; i < N; ++i){
            if (i == X) continue;
            max = Math.max(max, d[i][X] + d[X][i]);
        }
        System.out.println(max);
    }

    private static void dijkstra(int s){
        int V = N;
        boolean[] used = new boolean[V];
        while (true){
            int v = -1;
            for (int i = 0; i < V; ++i){
                if (!used[i] && (v == -1 || d[s][i] < d[s][v])) v = i;
            }
            if (v == -1) break;
            used[v] = true;
            for (int i = 0; i < V; ++i){
                d[s][i] = Math.min(d[s][i],d[s][v] + g[v][i]);
            }
        }
    }

求任意两点之间的距离,需要对djkstra做些改变,但依旧TLE了,呵呵,这是因为算法复杂度还是为 O(n3) ,DIJKSTRA算法的复杂度为 O(n2) ,这和我们使用邻接矩阵有关系,我们在更新时,并不知道谁和谁到底是相连的,所以还是更新每条边。

所以与其这样,我们不如优化我们的存储图的结构,邻接表是很好的选择,对于每个顶点,都知道与它相连的顶点的是谁,起码比上述做法要快很多。

还有一点,每次求最小值需要 O(n) ?在动态结构下,每次求最小,我们有一种 O(logn) 的结构,没错,就是堆,所以这才是DIJKSTRA算法快的真正原因,最小值的维护用优先队列就可以了。而之所以能用最小值,前文已经叙述过了。

代码如下:

static int INF = 1 << 29;
    static int[][] d;
    static List<Edge>[] graph;
    static int N;

    static class Edge{
        int from;
        int to;
        int cost;

        @Override
        public String toString() {
            return from + "->" + to + " ,cost: " + cost;
        }
    }

    public static void main(String[] args) throws IOException {
        Scanner in = new Scanner(System.in);
        N = in.nextInt();

        int M = in.nextInt();
        int X = in.nextInt();
        graph = new ArrayList[N];

        d = new int[N][N];
        for (int i = 0; i < N; ++i) graph[i] = new ArrayList<>();

        for (int i = 0; i < M; ++i){
            int f = in.nextInt();
            int t = in.nextInt();
            f--;
            t--;
            int c = in.nextInt();
            Edge e = new Edge();
            e.from = f;
            e.to = t;
            e.cost = c;
            graph[e.from].add(e);
        }

        for (int i = 0; i < N; ++i){
            //initial
            Arrays.fill(d[i], INF);
            d[i][i] = 0;
            dijkstra(i);
        }

        int max = 0;
        for (int i = 0; i < N; ++i){
            if (i == X) continue;
            max = Math.max(max, d[i][X] + d[X][i]);
        }
        System.out.println(max);
    }

    private static void dijkstra(int s){
        int V = N;
        IndexMinPQ<Integer> pq = new IndexMinPQ<>(V);
        pq.insert(s, d[s][s]);
        while (!pq.isEmpty()){
            int v = pq.delMin();
            for (Edge e : graph[v]){
                int from = e.from;
                int to = e.to;
                int cost = e.cost;
                if (d[s][from] + cost < d[s][to]){
                    d[s][to] = d[s][from] + cost;
                    if (pq.contains(to)) pq.decreaseKey(to, d[s][to]);
                    else pq.insert(to, d[s][to]);
                }
            }
        }
    }

这里的IndexMinPQ是一种特殊的优先队列,可以支持索引查找元素以及在更新元素大小后,自动上沉下浮,此处就不在阐述了。

唉,这道题还可以更快,可以利用无向图的性质来做这件事,这样两次dijkstra就完事了,说下思路吧。

  • 从X到每个顶点的最短距离,一次dijkstra(X)即可。
  • 如果是无向图,任何顶点到X的最短距离,一定也是X到任何顶点的最短距离,所以此图可以来个反向,这样相当于求无向图中的顶点X到任何顶点的最短距离,再来一次dijkstra(X),OK!

AOJ 2249: Road Construction

给一版完整的实现,这道题是求最短路径相同的那些边的cost最小,所以先用dijkstra求一次最短路径,接着找到所有最短路径的边,选择cost最小的边即可。

刚开始尝试了邻接矩阵的Dijkstra,结果MLE了,呵呵哒。只不过这种Dijkstra算法速度和Floyd算法差不多,还不如用堆+邻接表。

完整代码如下:

public class SolutionDay16_A2249 {

    static class Edge{
        int from;
        int to;
        int dist;
        int cost;

        @Override
        public String toString() {
            return "{"+from + "->" + to + ", dist: " + dist + "}";
        }
    }

    static int INF = 1 << 29;
    static List<Edge>[] graph;
    static int[] dist;
    static int N;

    @SuppressWarnings("unchecked")
    public static void main(String[] args) throws IOException {
        Scanner in = new Scanner(System.in);
        while (true){
            N = in.nextInt();
            int M = in.nextInt();
            if (N == 0 && M == 0) break;
            graph = new ArrayList[N];
            for (int i = 0; i < N; ++i) graph[i] = new ArrayList<>();

            for (int i = 0; i < M; ++i){
                int f = in.nextInt();
                int t = in.nextInt();
                f--;
                t--;
                int dist = in.nextInt();
                int cost = in.nextInt();

                Edge edge = new Edge();
                edge.from = f;
                edge.to = t;
                edge.cost = cost;
                edge.dist = dist;
                graph[edge.from].add(edge);

                edge = new Edge();
                edge.from = t;
                edge.to = f;
                edge.cost = cost;
                edge.dist = dist;
                graph[edge.from].add(edge);
            }

            dijkstra(0);
            long sum = 0;
            for (int i = 1; i < N; ++i){
                int min = INF;
                for (Edge e : graph[i]){
                    if (dist[i] == dist[e.to] + e.dist && e.cost < min){
                        min = e.cost;
                    }
                }
                sum += min;
            }
            System.out.println(sum);
        }
    }

    private static void dijkstra(int s){
        dist = new int[N];
        Arrays.fill(dist,INF);
        dist[s] = 0;
        IndexMinPQ<Integer> pq = new IndexMinPQ<>(N);
        pq.insert(s, dist[s]);
        while (!pq.isEmpty()){
            int v = pq.delMin();
            for (Edge e : graph[v]){
                if (dist[e.from] + e.dist < dist[e.to]){
                    dist[e.to] = dist[e.from] + e.dist;
                    if (pq.contains(e.to)) pq.decreaseKey(e.to, dist[e.to]);
                    else pq.insert(e.to, dist[e.to]);
                }
            }
        }
    }

    static class Scanner {

        private BufferedReader br;
        private StringTokenizer tok;

        public Scanner(InputStream is) throws IOException {
            br = new BufferedReader(new InputStreamReader(is));
            getLine();
        }

        private void getLine() throws IOException {
            while (tok == null || !tok.hasMoreTokens()) {
                tok = new StringTokenizer(br.readLine());
            }
        }

        private boolean hasNext() {
            return tok.hasMoreTokens();
        }

        public String next() throws IOException {
            if (hasNext()) {
                return tok.nextToken();
            } else {
                getLine();
                return tok.nextToken();
            }
        }

        public int nextInt() throws IOException {
            if (hasNext()) {
                return Integer.parseInt(tok.nextToken());
            } else {
                getLine();
                return Integer.parseInt(tok.nextToken());
            }
        }

        public long nextLong() throws IOException {
            if (hasNext()) {
                return Long.parseLong(tok.nextToken());
            } else {
                getLine();
                return Long.parseLong(tok.nextToken());
            }
        }

        public double nextDouble() throws IOException {
            if (hasNext()) {
                return Double.parseDouble(tok.nextToken());
            } else {
                getLine();
                return Double.parseDouble(tok.nextToken());
            }
        }
    }

    static class IndexMinPQ<Key extends Comparable<Key>> implements Iterable<Integer> {
        private int maxN;        // maximum number of elements on PQ
        private int n;           // number of elements on PQ
        private int[] pq;        // binary heap using 1-based indexing
        private int[] qp;        // inverse of pq - qp[pq[i]] = pq[qp[i]] = i
        private Key[] keys;      // keys[i] = priority of i

        /**
         * Initializes an empty indexed priority queue with indices between {@code 0}
         * and {@code maxN - 1}.
         * @param  maxN the keys on this priority queue are index from {@code 0}
         *         {@code maxN - 1}
         * @throws IllegalArgumentException if {@code maxN < 0}
         */
        @SuppressWarnings("unchecked")
        public IndexMinPQ(int maxN) {
            if (maxN < 0) throw new IllegalArgumentException();
            this.maxN = maxN;
            n = 0;
            keys = (Key[]) new Comparable[maxN + 1];    // make this of length maxN??
            pq   = new int[maxN + 1];
            qp   = new int[maxN + 1];                   // make this of length maxN??
            for (int i = 0; i <= maxN; i++)
                qp[i] = -1;
        }

        /**
         * Returns true if this priority queue is empty.
         *
         * @return {@code true} if this priority queue is empty;
         *         {@code false} otherwise
         */
        public boolean isEmpty() {
            return n == 0;
        }

        /**
         * Is {@code i} an index on this priority queue?
         *
         * @param  i an index
         * @return {@code true} if {@code i} is an index on this priority queue;
         *         {@code false} otherwise
         * @throws IndexOutOfBoundsException unless {@code 0 <= i < maxN}
         */
        public boolean contains(int i) {
            if (i < 0 || i >= maxN) throw new IndexOutOfBoundsException();
            return qp[i] != -1;
        }

        /**
         * Returns the number of keys on this priority queue.
         *
         * @return the number of keys on this priority queue
         */
        public int size() {
            return n;
        }

        /**
         * Associates key with index {@code i}.
         *
         * @param  i an index
         * @param  key the key to associate with index {@code i}
         * @throws IndexOutOfBoundsException unless {@code 0 <= i < maxN}
         * @throws IllegalArgumentException if there already is an item associated
         *         with index {@code i}
         */
        public void insert(int i, Key key) {
            if (i < 0 || i >= maxN) throw new IndexOutOfBoundsException();
            if (contains(i)) throw new IllegalArgumentException("index is already in the priority queue");
            n++;
            qp[i] = n;
            pq[n] = i;
            keys[i] = key;
            swim(n);
        }

        /**
         * Returns an index associated with a minimum key.
         *
         * @return an index associated with a minimum key
         * @throws NoSuchElementException if this priority queue is empty
         */
        public int minIndex() {
            if (n == 0) throw new NoSuchElementException("Priority queue underflow");
            return pq[1];
        }

        /**
         * Returns a minimum key.
         *
         * @return a minimum key
         * @throws NoSuchElementException if this priority queue is empty
         */
        public Key minKey() {
            if (n == 0) throw new NoSuchElementException("Priority queue underflow");
            return keys[pq[1]];
        }

        /**
         * Removes a minimum key and returns its associated index.
         * @return an index associated with a minimum key
         * @throws NoSuchElementException if this priority queue is empty
         */
        public int delMin() {
            if (n == 0) throw new NoSuchElementException("Priority queue underflow");
            int min = pq[1];
            exch(1, n--);
            sink(1);
            assert min == pq[n+1];
            qp[min] = -1;        // delete
            keys[min] = null;    // to help with garbage collection
            pq[n+1] = -1;        // not needed
            return min;
        }

        /**
         * Returns the key associated with index {@code i}.
         *
         * @param  i the index of the key to return
         * @return the key associated with index {@code i}
         * @throws IndexOutOfBoundsException unless {@code 0 <= i < maxN}
         * @throws NoSuchElementException no key is associated with index {@code i}
         */
        public Key keyOf(int i) {
            if (i < 0 || i >= maxN) throw new IndexOutOfBoundsException();
            if (!contains(i)) throw new NoSuchElementException("index is not in the priority queue");
            else return keys[i];
        }

        /**
         * Change the key associated with index {@code i} to the specified value.
         *
         * @param  i the index of the key to change
         * @param  key change the key associated with index {@code i} to this key
         * @throws IndexOutOfBoundsException unless {@code 0 <= i < maxN}
         * @throws NoSuchElementException no key is associated with index {@code i}
         */
        public void changeKey(int i, Key key) {
            if (i < 0 || i >= maxN) throw new IndexOutOfBoundsException();
            if (!contains(i)) throw new NoSuchElementException("index is not in the priority queue");
            keys[i] = key;
            swim(qp[i]);
            sink(qp[i]);
        }

        /**
         * Change the key associated with index {@code i} to the specified value.
         *
         * @param  i the index of the key to change
         * @param  key change the key associated with index {@code i} to this key
         * @throws IndexOutOfBoundsException unless {@code 0 <= i < maxN}
         * @deprecated Replaced by {@code changeKey(int, Key)}.
         */
        @Deprecated
        public void change(int i, Key key) {
            changeKey(i, key);
        }

        /**
         * Decrease the key associated with index {@code i} to the specified value.
         *
         * @param  i the index of the key to decrease
         * @param  key decrease the key associated with index {@code i} to this key
         * @throws IndexOutOfBoundsException unless {@code 0 <= i < maxN}
         * @throws IllegalArgumentException if {@code key >= keyOf(i)}
         * @throws NoSuchElementException no key is associated with index {@code i}
         */
        public void decreaseKey(int i, Key key) {
            if (i < 0 || i >= maxN) throw new IndexOutOfBoundsException();
            if (!contains(i)) throw new NoSuchElementException("index is not in the priority queue");
            if (keys[i].compareTo(key) <= 0)
                throw new IllegalArgumentException("Calling decreaseKey() with given argument would not strictly decrease the key");
            keys[i] = key;
            swim(qp[i]);
        }

        /**
         * Increase the key associated with index {@code i} to the specified value.
         *
         * @param  i the index of the key to increase
         * @param  key increase the key associated with index {@code i} to this key
         * @throws IndexOutOfBoundsException unless {@code 0 <= i < maxN}
         * @throws IllegalArgumentException if {@code key <= keyOf(i)}
         * @throws NoSuchElementException no key is associated with index {@code i}
         */
        public void increaseKey(int i, Key key) {
            if (i < 0 || i >= maxN) throw new IndexOutOfBoundsException();
            if (!contains(i)) throw new NoSuchElementException("index is not in the priority queue");
            if (keys[i].compareTo(key) >= 0)
                throw new IllegalArgumentException("Calling increaseKey() with given argument would not strictly increase the key");
            keys[i] = key;
            sink(qp[i]);
        }

        /**
         * Remove the key associated with index {@code i}.
         *
         * @param  i the index of the key to remove
         * @throws IndexOutOfBoundsException unless {@code 0 <= i < maxN}
         * @throws NoSuchElementException no key is associated with index {@code i}
         */
        public void delete(int i) {
            if (i < 0 || i >= maxN) throw new IndexOutOfBoundsException();
            if (!contains(i)) throw new NoSuchElementException("index is not in the priority queue");
            int index = qp[i];
            exch(index, n--);
            swim(index);
            sink(index);
            keys[i] = null;
            qp[i] = -1;
        }


       /***************************************************************************
        * General helper functions.
        ***************************************************************************/
        private boolean greater(int i, int j) {
            return keys[pq[i]].compareTo(keys[pq[j]]) > 0;
        }

        private void exch(int i, int j) {
            int swap = pq[i];
            pq[i] = pq[j];
            pq[j] = swap;
            qp[pq[i]] = i;
            qp[pq[j]] = j;
        }


       /***************************************************************************
        * Heap helper functions.
        ***************************************************************************/
        private void swim(int k) {
            while (k > 1 && greater(k/2, k)) {
                exch(k, k/2);
                k = k/2;
            }
        }

        private void sink(int k) {
            while (2*k <= n) {
                int j = 2*k;
                if (j < n && greater(j, j+1)) j++;
                if (!greater(k, j)) break;
                exch(k, j);
                k = j;
            }
        }


       /***************************************************************************
        * Iterators.
        ***************************************************************************/

        /**
         * Returns an iterator that iterates over the keys on the
         * priority queue in ascending order.
         * The iterator doesn't implement {@code remove()} since it's optional.
         *
         * @return an iterator that iterates over the keys in ascending order
         */
        public Iterator<Integer> iterator() { return new HeapIterator(); }

        private class HeapIterator implements Iterator<Integer> {
            // create a new pq
            private IndexMinPQ<Key> copy;

            // add all elements to copy of heap
            // takes linear time since already in heap order so no keys move
            public HeapIterator() {
                copy = new IndexMinPQ<Key>(pq.length - 1);
                for (int i = 1; i <= n; i++)
                    copy.insert(pq[i], keys[pq[i]]);
            }

            public boolean hasNext()  { return !copy.isEmpty();                     }
            public void remove()      { throw new UnsupportedOperationException();  }

            public Integer next() {
                if (!hasNext()) throw new NoSuchElementException();
                return copy.delMin();
            }
        }
    }

}

勉强过了,速度还算可以。当然,我们完全可以借助Java自带的priorityQueue来实现,也非常容易理解,虽然会有重复id的结点,但这些顶点早就已经更新到了最小值,所以把它们poll出来后,不会再有新的元素push进去,利用这一点就无需在push的时候进行元素的上沉下浮。

代码如下:

static class Edge{
        int from;
        int to;
        int dist;
        int cost;

        @Override
        public String toString() {
            return "{"+from + "->" + to + ", dist: " + dist + "}";
        }
    }

    static int INF = 1 << 29;
    static List<Edge>[] graph;
    static int[] dist;
    static int N;

    @SuppressWarnings("unchecked")
    public static void main(String[] args) throws IOException {
        Scanner in = new Scanner(System.in);
        while (true){
            N = in.nextInt();
            int M = in.nextInt();
            if (N == 0 && M == 0) break;
            graph = new ArrayList[N];
            for (int i = 0; i < N; ++i) graph[i] = new ArrayList<>();

            for (int i = 0; i < M; ++i){
                int f = in.nextInt();
                int t = in.nextInt();
                f--;
                t--;
                int dist = in.nextInt();
                int cost = in.nextInt();

                Edge edge = new Edge();
                edge.from = f;
                edge.to = t;
                edge.cost = cost;
                edge.dist = dist;
                graph[edge.from].add(edge);

                edge = new Edge();
                edge.from = t;
                edge.to = f;
                edge.cost = cost;
                edge.dist = dist;
                graph[edge.from].add(edge);
            }

            dijkstra(0);
            long sum = 0;
            for (int i = 1; i < N; ++i){
                int min = INF;
                for (Edge e : graph[i]){
                    if (dist[i] == dist[e.to] + e.dist && e.cost < min){
                        min = e.cost;
                    }
                }
                sum += min;
            }
            System.out.println(sum);
        }
    }

    static class Node implements Comparable<Node>{
        int id;
        int dist;
        public Node(int id, int dist){
            this.id = id;
            this.dist = dist;
        }

        @Override
        public int compareTo(Node o) {
            return this.dist - o.dist;
        }
    }

    private static void dijkstra(int s){
        dist = new int[N];
        Arrays.fill(dist,INF);
        dist[s] = 0;
        Queue<Node> pq = new PriorityQueue<>();
        pq.offer(new Node(s,dist[s]));
        while (!pq.isEmpty()){
            int v = pq.poll().id;
            for (Edge e : graph[v]){
                if (e.dist + dist[e.from] < dist[e.to]){
                    dist[e.to] = e.dist + dist[e.from];
                    pq.offer(new Node(e.to, dist[e.to]));
                }
            }
        }
    }

AOJ 2200: Mr. Rito Post Office

一道DP题,翻译可以参看博文AOJ 2200 Mr. Rito Post Office 题解 《挑战程序设计竞赛》,这道题目很有趣,分为水路和陆地,两种不同的状态,而且只有一艘船。

问题简化,首先,一定是按照顺寻每个地点进行寄快递,所以如果没有水路的话,可以直接求出任意两点之间的最短距离,每寄送一次,就把路径加上。

所以第一步一定是把两点间的最短路径分别求出来。(水路和陆地)

但问题来了,当遇到水路和陆地同时存在的情况?快递员就头大了,到底选择哪种方式去寄送快呢?如果就从局部来看,可以尝试贪心,每当进入一个城市后,如果当前城市有船,就尝试走水路和陆地,谁小,就取谁。而如果当前城市没船,则没有选择,直接走陆地。

我试着用这种方法去做,但发现答案并不对,这只能说明局部最优解不能推得整体最优。

既然这样,该dp就要复杂了,不是两个状态(有船or没船),而是在当前城市下,我的船在哪个城市。

所以我们有:

dp[i][j] 表示快递分送到第i个城市时,小船在城市j的最短路径。

这种多状态就好比,小船有了好多个虚拟分身,遍布在各大城市,的确高明。

现在假定有了dp[i-1][k]这样一个状态,如何更新到dp[i][j]

很简单,两种情况:
a. 小船的位置k == j,说明小船不需要移动,直接走陆地。
b. 小船的位置k != j,说明现在小船在其它城市,所以必须从i-1出发到城市k,在开船到j,把船停到j,再从陆地出发到i。

最后,求小船在不同位置时,状态i下的最小值即可。

代码如下:

static int[][] water;
    static int[][] land;
    static int N;
    static int INF = 1 << 28;
    public static void main(String[] args) throws IOException {
        Scanner in = new Scanner(System.in);
        while (true){
            N = in.nextInt();
            int M = in.nextInt();
            if (N == 0 && M == 0) break;

            water = new int[N][N];
            land = new int[N][N];

            for (int i = 0; i < N; ++i){
                Arrays.fill(water[i], INF);
                Arrays.fill(land[i],INF);
                water[i][i] = 0;
                land[i][i] = 0;
            }

            for (int i = 0; i < M; ++i){
                int from = in.nextInt();
                int to = in.nextInt();
                from--;
                to--;

                int cost = in.nextInt();
                String mark = in.next();
                if (mark.equals("S")){
                    water[from][to] = water[to][from] = cost;
                }
                else{
                    land[from][to] = land[to][from] = cost;
                }
            }

            int C = in.nextInt();
            int[] city = new int[C];
            for (int i = 0; i < C; ++i){
                city[i] = in.nextInt() - 1;
            }

            warshallFloyd();

            int[][] dp = new int[C][N];
            for (int i = 0; i < C; ++i) Arrays.fill(dp[i], INF);
            for (int i = 0; i < N; ++i){
                dp[0][i] = land[city[0]][i] + water[i][city[0]];
            }

            for (int i = 1; i < C; ++i){
                for (int j = 0; j < N; ++j){
                    for (int k = 0; k < N; ++k){
                        if (j != k){
                            dp[i][k] = Math.min(dp[i][k], dp[i-1][j] + land[city[i-1]][j] + water[j][k] + land[k][city[i]]);
                        }
                        else{
                            dp[i][k] = Math.min(dp[i][k], dp[i-1][j] + land[city[i-1]][city[i]]);
                        }
                    }
                }
            }

            int min = INF;
            for (int i = 0; i < N; ++i){
                min = Math.min(min, dp[C-1][i]);
            }

            System.out.println(min);
        }
    }

    private static void warshallFloyd(){ 
        for (int i = 0; i < N; ++i){
            for (int j = 0; j < N; ++j){
                for (int k = 0; k < N; ++k){
                    water[j][k] = Math.min(water[j][k], water[j][i] + water[i][k]);
                    land[j][k] = Math.min(land[j][k], land[j][i] + land[i][k]);
                }
            }
        }
    }

如果能够联想到动规,这问题不难解决,但为什么就是动规呢?就从这道题来看,因为小船的位置无法根据当前状态【准确的】转移到下一状态,与其这样,我们不如让小船在各个位置都发生,所谓的多状态。这就好比小船的分身,虽然现实中并不是在一个阶段同时出现,但在问题求解过程中,我们可以假设小船在一个阶段中有多个状态,而走到最后一个状态时,再把所有小船消灭掉,留下一个最优的。孙悟空一吹猴毛,是为了打架,最后不还是要回归真身?有趣。

POJ 3259: Wormholes

有趣的虫洞,时间倒流?其实就是一个负环检测问题。负环检测,用到了Bellman算法,核心思想是说,在第V轮,若还在更新的话,说明检测到了负环。(这是一个充分必要条件,要证明还是比较麻烦。)

简单来说,一条简单最短路径(无0环),若有V个顶点,必然有V-1条边,所以更新V-1次,该路径就不会再有变化了,第一次给了源点d[s] = 0。这是最直观帮助我理解负环检测的算法,正确性还需要再探讨。

代码如下:

static class Edge{
        int from;
        int to;
        int cost;

        public String toString(){
            return from + "->" + to + " (" + cost + ")";
        }
    }

    public static void main(String[] args) throws IOException {
        Scanner in = new Scanner(System.in);
        int F = in.nextInt();
        while (F-- != 0){
            int V = in.nextInt();
            int p = in.nextInt();
            int w = in.nextInt();
            Edge[] edges = new Edge[2 * p + w];
            int E = 0;
            for (int i = 0; i < p; ++i){
                int from = in.nextInt();
                int to = in.nextInt();
                int cost = in.nextInt();
                from--;
                to--;

                Edge edge = new Edge();
                edge.from = from;
                edge.to = to;
                edge.cost = cost;
                edges[E++] = edge;

                edge = new Edge();
                edge.from = to;
                edge.to = from;
                edge.cost = cost;
                edges[E++] = edge;
            }

            for (int i = 0; i < w; ++i){
                int from = in.nextInt();
                int to = in.nextInt();
                int cost = in.nextInt();
                from--;
                to--;
                cost = -cost;

                Edge edge = new Edge();
                edge.from = from;
                edge.to = to;
                edge.cost = cost;
                edges[E++] = edge;
            }

            System.out.println(findNegativeCycle(edges, V) ? "YES" : "NO");
        }
    }

    static final int INF = 1 << 29;
    private static boolean findNegativeCycle(Edge[] edges, int V){
        int[] d = new int[V];
        for (int i = 0; i < V; ++i){
            for (Edge e : edges){
                if(e.cost + d[e.from] < d[e.to]){
                    d[e.to] = e.cost + d[e.from];
                    if (i == V - 1) return true;
                }
            }
        }
        return false;
    }

累,休息。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值