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

来源:互联网 发布:华为光猫开启端口23 编辑:程序博客网 时间:2024/06/06 01:43

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

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

练习题如下:

  • AOJ 0189: Convenient Location
  • POJ 2139: Six Degrees of Cowvin Bacon
  • POJ 3268: Sliver Cow Party
  • AOJ 2249: Road Construction
  • AOJ 2200: Mr. Rito Post Office
  • POJ 3259: Wormholes

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;    }

累,休息。

原创粉丝点击