挑战程序竞赛系列(29):3.4熟练掌握动态规划
2017-07-27 22:06
603 查看
挑战程序竞赛系列(29):3.4熟练掌握动态规划
详细代码可以fork下Github上leetcode项目,不定期更新。练习题如下:
POJ 2686: Travelling by Stagecoach
POJ 2441: Arrange the Bulls
POJ 3254: Corn Fields
POJ 2836: Rectangle Covering
POJ 3411: Paid Roads
POJ 1795: DNA Laboratory
POJ 2441: Arrange the Bulls
开始状态压缩,做了几道,发现状态压缩都是带记忆的暴力枚举。解决问题的关键在对状态的抽象,从而可以降低直接暴力枚举的时间复杂度。这些题目往往都是些NP问题,如旅行商问题。此题思路:对于车票而言,用一张少一张,很明显的一个阶段(DAG),所以不会走环路,那么用简单的DP就能解决。问题在于阶段中所有状态该如何寻找,很明显从城市a开始,因为此时没有使用任何车票,所以可以枚举任何一张车票和与a相连的城市状态,总共有:(剩余车票数 * 与城市a连接的城市总数)个状态,那么可以构造:
dp[s][v] // s表示当前剩余的所有车票,v表示已经抵达的目的城市,值存放了从a到v的最短路径
代码如下:
import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.io.PrintWriter; import java.util.Arrays; import java.util.InputMismatchException; public class Main{ InputStream is; PrintWriter out; String INPUT = "./data/judge/201707/2686.txt"; static final int INF = 1 << 29; void solve() { while (true){ int n = ni(); int m = ni(); int p = ni(); int a = ni(); int b = ni(); if (n + m + p + a + b == 0) break; a --; b --; int[] tickets = new int ; for (int i = 0; i < n; ++i){ tickets[i] = ni(); } int[][] graph = new int[m][m]; for (int i = 0; i < m; ++i) Arrays.fill(graph[i], -1); for (int i = 0; i < p; ++i){ int from = ni(); int to = ni(); from --; to --; int dist = ni(); graph[from][to] = dist; graph[to][from] = dist; } double[][] dp = new double[1 << n][m + 16]; for (int i = 0; i < (1 << n); ++i) Arrays.fill(dp[i], INF); dp[(1 << n) - 1][a] = 0; //从城市a出发,且全票情况下的最短路径 double res = INF; for (int s = (1 << n) - 1; s >= 0; --s){ res = Math.min(res, dp[s]); for (int v = 0; v < m; ++v){ for (int t = 0; t < n; ++t){ //票还在集合当中,则从集合删除 if (((s >> t) & 1) != 0){ for (int u = 0; u < m; ++u){ if (graph[u][v] > 0){ dp[s & ~(1 << t)][v] = Math.min(dp[s & ~(1 << t)][v], dp[s][u] + graph[u][v] / (1.0 * tickets[t])); } } } } } } if (res == INF){ out.println("Impossible"); } else{ out.printf("%.3f\n", res); } } } void run() throws Exception { is = oj ? System.in : new FileInputStream(new File(INPUT)); out = new PrintWriter(System.out); long s = System.currentTimeMillis(); solve(); out.flush(); tr(System.currentTimeMillis() - s + "ms"); } public static void main(String[] args) throws Exception { new Main().run(); } private byte[] inbuf = new byte[1024]; public int lenbuf = 0, ptrbuf = 0; private int readByte() { if (lenbuf == -1) throw new InputMismatchException(); if (ptrbuf >= lenbuf) { ptrbuf = 0; try { lenbuf = is.read(inbuf); } catch (IOException e) { throw new InputMismatchException(); } if (lenbuf <= 0) return -1; } return inbuf[ptrbuf++]; } private boolean isSpaceChar(int c) { return !(c >= 33 && c <= 126); } private int skip() { int b; while ((b = readByte()) != -1 && isSpaceChar(b)) ; return b; } private double nd() { return Double.parseDouble(ns()); } private char nc() { return (char) skip(); } private String ns() { int b = skip(); StringBuilder sb = new StringBuilder(); while (!(isSpaceChar(b))) { // when nextLine, (isSpaceChar(b) && b != ' // ') sb.appendCodePoint(b); b = readByte(); } return sb.toString(); } private char[] ns(int n) { char[] buf = new char ; int b = skip(), p = 0; while (p < n && !(isSpaceChar(b))) { buf[p++] = (char) b; b = readByte(); } return n == p ? buf : Arrays.copyOf(buf, p); } private char[][] nm(int n, int m) { char[][] map = new char []; for (int i = 0; i < n; i++) map[i] = ns(m); return map; } private int[] na(int n) { int[] a = new int ; for (int i = 0; i < n; i++) a[i] = ni(); return a; } private int ni() { int num = 0, b; boolean minus = false; while ((b = readByte()) != -1 && !((b >= '0' && b <= '9') || b == '-')) ; if (b == '-') { minus = true; b = readByte(); } while (true) { if (b >= '0' && b <= '9') { num = num * 10 + (b - '0'); } else { return minus ? -num : num; } b = readByte(); } } private long nl() { long num = 0; int b; boolean minus = false; while ((b = readByte()) != -1 && !((b >= '0' && b <= '9') || b == '-')) ; if (b == '-') { minus = true; b = readByte(); } while (true) { if (b >= '0' && b <= '9') { num = num * 10 + (b - '0'); } else { return minus ? -num : num; } b = readByte(); } } private boolean oj = System.getProperty("ONLINE_JUDGE") != null; private void tr(Object... o) { if (!oj) System.out.println(Arrays.deepToString(o)); } }
[b]POJ 2441: Arrange the Bulls
此题经历了三个版本,分别MLE,TLE,最后才AC。思路比较简单,每头牛的选择只与剩余地点集合有关,所以只要在选择时,确保该地点没有被选择,那么这头牛就可以选则这球场,并且是所有可选状态的总和。可以想象,选定即占位,对于后来的牛来说,并不在乎谁选了哪些球场,只在乎还有多少球场可选。代码如下:
void solve() { int N = ni(); int M = ni(); List<Integer>[] g = new ArrayList ; for (int i = 0; i < N; ++i) g[i] = new ArrayList<Integer>(); for (int i = 0; i < N; ++i){ int m = ni(); for (int j = 0; j < m; ++j){ g[i].add(ni() - 1); } } int[][] dp = new int [1 << M]; //阶段1 for (int barn : g[0]){ dp[0][0 | (1 << barn)] = 1; } for (int i = 1; i < N; ++i){ for (int barn : g[i]){ for (int s = 0; s < (1 << M); ++s){ if ((s >> barn & 1) == 0){ dp[i][s | (1 << barn)] += dp[(i - 1)][s]; } } } } int sum = 0; for (int s = 0; s < (1 << M); ++s){ sum += dp[N - 1][s]; } out.println(sum); }
数组开的太大,MLE了,此题的特色在于当前阶段之和前一阶段有关,所以可以采用滚动数组,代码如下:
void solve() { int N = ni(); int M = ni(); List<Integer>[] g = new ArrayList ; for (int i = 0; i < N; ++i) g[i] = new ArrayList<Integer>(); for (int i = 0; i < N; ++i){ int m = ni(); for (int j = 0; j < m; ++j){ g[i].add(ni() - 1); } } int[][] dp = new int[2][1 << M]; //阶段1 for (int barn : g[0]){ dp[0][0 | (1 << barn)] = 1; } for (int i = 1; i < N; ++i){ for (int barn : g[i]){ for (int s = 0; s < (1 << M); ++s){ if ((s >> barn & 1) == 0){ dp[i % 2][s | (1 << barn)] += dp[(i - 1) % 2][s]; } } } Arrays.fill(dp[(i - 1) % 2], 0); } int sum = 0; for (int s = 0; s < (1 << M); ++s){ sum += dp[(N - 1) % 2][s]; } out.println(sum); }
TLE了,可以观察下循环,原因在于对每个阶段,都会有很多无效状态参与计算,很显然那些只会在阶段i+1之后出现的状态没有必要遍历,所以我们必须采取遍历大小为i的所有子集的算法。
《挑战》给了我们一个很好的算法:
嘿,在书P157讲的很详细,我们用到了枚举大小为k的子集方法:
for (int comb = (1 << i) - 1, x, y; comb < 1 << M; x = comb & -comb, y = comb + x, comb = ((comb & ~y) / x >> 1) | y){ //遍历所有大小为k的子集,按升序遍历 }
原理可以参看书中的分析,主要是先找出最低位的1,接着把地位连续1全部变成0,接着检测出所有由1变成0的连续1,把它们的个数记录下来,移动到最右端和comb或一下,得到结果。
代码如下:
import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.io.PrintWriter; import java.util.ArrayList; import java.util.Arrays; import java.util.InputMismatchException; import java.util.List; public class Main{ InputStream is; PrintWriter out; String INPUT = "./data/judge/201707/2441.txt"; void solve() { int N = ni(); int M = ni(); List<Integer>[] g = new ArrayList ; for (int i = 0; i < N; ++i) g[i] = new ArrayList<Integer>(); for (int i = 0; i < N; ++i){ int m = ni(); for (int j = 0; j < m; ++j){ g[i].add(ni() - 1); } } int[] dp = new int[1 << M]; for (int u : g[0]){ dp[0 | 1 << u] = 1; } for (int i = 1; i < N; ++i){ for (int comb = (1 << i) - 1, x, y; comb < 1 << M; x = comb & -comb, y = comb + x, comb = ((comb & ~y) / x >> 1) | y){ if (dp[comb] != 0){ for (int j : g[i]){ if ((comb & 1 << j) == 0){ dp[comb | (1 << j)] += dp[comb]; } } } } } int sum = 0; for (int comb = (1 << N) - 1, x, y; comb < 1 << M; x = comb & -comb, y = comb + x, comb = ((comb & ~y) / x >> 1) | y){ sum += dp[comb]; } out.println(sum); } void run() throws Exception { is = oj ? System.in : new FileInputStream(new File(INPUT)); out = new PrintWriter(System.out); long s = System.currentTimeMillis(); solve(); out.flush(); tr(System.currentTimeMillis() - s + "ms"); } public static void main(String[] args) throws Exception { new Main().run(); } private byte[] inbuf = new byte[1024]; public int lenbuf = 0, ptrbuf = 0; private int readByte() { if (lenbuf == -1) throw new InputMismatchException(); if (ptrbuf >= lenbuf) { ptrbuf = 0; try { lenbuf = is.read(inbuf); } catch (IOException e) { throw new InputMismatchException(); } if (lenbuf <= 0) return -1; } return inbuf[ptrbuf++]; } private boolean isSpaceChar(int c) { return !(c >= 33 && c <= 126); } private int skip() { int b; while ((b = readByte()) != -1 && isSpaceChar(b)) ; return b; } private double nd() { return Double.parseDouble(ns()); } private char nc() { return (char) skip(); } private String ns() { int b = skip(); StringBuilder sb = new StringBuilder(); while (!(isSpaceChar(b))) { // when nextLine, (isSpaceChar(b) && b != ' // ') sb.appendCodePoint(b); b = readByte(); } return sb.toString(); } private char[] ns(int n) { char[] buf = new char ; int b = skip(), p = 0; while (p < n && !(isSpaceChar(b))) { buf[p++] = (char) b; b = readByte(); } return n == p ? buf : Arrays.copyOf(buf, p); } private char[][] nm(int n, int m) { char[][] map = new char []; for (int i = 0; i < n; i++) map[i] = ns(m); return map; } private int[] na(int n) { int[] a = new int ; for (int i = 0; i < n; i++) a[i] = ni(); return a; } private int ni() { int num = 0, b; boolean minus = false; while ((b = readByte()) != -1 && !((b >= '0' && b <= '9') || b == '-')) ; if (b == '-') { minus = true; b = readByte(); } while (true) { if (b >= '0' && b <= '9') { num = num * 10 + (b - '0'); } else { return minus ? -num : num; } b = readByte(); } } private long nl() { long num = 0; int b; boolean minus = false; while ((b = readByte()) != -1 && !((b >= '0' && b <= '9') || b == '-')) ; if (b == '-') { minus = true; b = readByte(); } while (true) { if (b >= '0' && b <= '9') { num = num * 10 + (b - '0'); } else { return minus ? -num : num; } b = readByte(); } } private boolean oj = System.getProperty("ONLINE_JUDGE") != null; private void tr(Object... o) { if (!oj) System.out.println(Arrays.deepToString(o)); } }
JAVA是真的比C++慢好几个数量级啊。。。
POJ 3254: Corn Fields
思路:依旧找阶段,最初定义阶段的方法,每次从集合中添加一个坑,但这种方式的状态转换不太好求,所以转换思路。枚举第一行所有可能的种植情况,并把第一行的所有状态记录下来,此时遍历第二行的所有可能状态,找出第一行和第二行合法状态的交集,即为答案。
判断第一行和第二行是否合法可以采用
if ((s1 & s2) == 0),这就表明不可能选择相邻元素,高明。
代码如下:
import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.io.PrintWriter; import java.util.Arrays; import java.util.InputMismatchException; public class Main{ InputStream is; PrintWriter out; String INPUT = "./data/judge/201707/3254.txt"; static final int MOD = 1000000000; int N = 0; int M = 0; void solve() { N = ni(); M = ni(); boolean[][] board = new boolean [M]; for (int i = 0; i < N; ++i){ for (int j = 0; j < M; ++j){ if (ni() == 1) board[i][j] = true; } } int[][] dp = new int [1 << M]; for (int i = 0; i < 1 << M; ++i){ if (valid(i, board[0])){ dp[0][i] = 1; } } for (int i = 1; i < N; ++i){ for (int j = 0; j < 1 << M; ++j){ if (valid(j, board[i])){ for (int s = 0; s < 1 << M; ++s){ if ((j & s) == 0){ dp[i][j] = (dp[i][j] + dp[i - 1][s]) % MOD; } } } } } int sum = 0; for (int i = 0; i < 1 << M; ++i){ if (valid(i, board[N - 1])){ sum = (sum + dp[N - 1][i]) % MOD; } } out.println(sum); } public boolean valid(int s, boolean[] board){ for (int i = 0; i < M; ++i){ if ((s & (1 << i)) != 0){ if (!board[i]) return false; if ((i + 1 < M) && (s & (1 << (i + 1))) != 0) return false; if ((i - 1 >= 0) && (s & (1 << (i - 1))) != 0) return false; } } return true; } void run() throws Exception { is = oj ? System.in : new FileInputStream(new File(INPUT)); out = new PrintWriter(System.out); long s = System.currentTimeMillis(); solve(); out.flush(); tr(System.currentTimeMillis() - s + "ms"); } public static void main(String[] args) throws Exception { new Main().run(); } private byte[] inbuf = new byte[1024]; public int lenbuf = 0, ptrbuf = 0; private int readByte() { if (lenbuf == -1) throw new InputMismatchException(); if (ptrbuf >= lenbuf) { ptrbuf = 0; try { lenbuf = is.read(inbuf); } catch (IOException e) { throw new InputMismatchException(); } if (lenbuf <= 0) return -1; } return inbuf[ptrbuf++]; } private boolean isSpaceChar(int c) { return !(c >= 33 && c <= 126); } private int skip() { int b; while ((b = readByte()) != -1 && isSpaceChar(b)) ; return b; } private double nd() { return Double.parseDouble(ns()); } private char nc() { return (char) skip(); } private String ns() { int b = skip(); StringBuilder sb = new StringBuilder(); while (!(isSpaceChar(b))) { // when nextLine, (isSpaceChar(b) && b != ' // ') sb.appendCodePoint(b); b = readByte(); } return sb.toString(); } private char[] ns(int n) { char[] buf = new char ; int b = skip(), p = 0; while (p < n && !(isSpaceChar(b))) { buf[p++] = (char) b; b = readByte(); } return n == p ? buf : Arrays.copyOf(buf, p); } private char[][] nm(int n, int m) { char[][] map = new char []; for (int i = 0; i < n; i++) map[i] = ns(m); return map; } private int[] na(int n) { int[] a = new int ; for (int i = 0; i < n; i++) a[i] = ni(); return a; } private int ni() { int num = 0, b; boolean minus = false; while ((b = readByte()) != -1 && !((b >= '0' && b <= '9') || b == '-')) ; if (b == '-') { minus = true; b = readByte(); } while (true) { if (b >= '0' && b <= '9') { num = num * 10 + (b - '0'); } else { return minus ? -num : num; } b = readByte(); } } private long nl() { long num = 0; int b; boolean minus = false; while ((b = readByte()) != -1 && !((b >= '0' && b <= '9') || b == '-')) ; if (b == '-') { minus = true; b = readByte(); } while (true) { if (b >= '0' && b <= '9') { num = num * 10 + (b - '0'); } else { return minus ? -num : num; } b = readByte(); } } private boolean oj = System.getProperty("ONLINE_JUDGE") != null; private void tr(Object... o) { if (!oj) System.out.println(Arrays.deepToString(o)); } }
POJ 2836: Rectangle Covering
思路:首先枚举所有点两两组合成的矩形,并且得到该矩形包含的所有点(反证法,必然两两组合的方案是最小矩形,且一定在这两点的边界上),有了这些矩形集合,就可以从状态0(没有任何点构成矩形)不断扩展到(所有点构成的矩形)。状态压缩的关键在于,对于每一个中间状态只记录衍生过来的最小值即可。代码如下:
import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.io.PrintWriter; import java.util.ArrayList; import java.util.Arrays; import java.util.InputMismatchException; import java.util.List; public class Main{ InputStream is; PrintWriter out; String INPUT = "./data/judge/201707/2386.txt"; class Rec{ int covered; int area; public Rec(int covered, int area){ this.covered = covered; this.area = area; } public void add(int i){ covered |= 1 << i; } } public boolean inRec(int[] a, int[] b, int[] p){ int minX = Math.min(a[0], b[0]); int maxX = Math.max(a[0], b[0]); int minY = Math.min(a[1], b[1]); int maxY = Math.max(a[1], b[1]); int x = p[0], y = p[1]; return x >= minX && x <= maxX && y >= minY && y <= maxY; } static final int INF = 0x3f3f3f3f; void solve() { while (true){ int n = ni(); if (n == 0) break; int[][] points = new int [2]; for (int i = 0; i < n; ++i){ int x = ni(); int y = ni(); points[i] = new int[]{x, y}; } List<Rec> recs = new ArrayList<Rec>(); for (int i = 0; i < n; ++i){ for (int j = i + 1; j < n; ++j){ Rec rec = new Rec(1 << i | 1 << j, Math.max(1, Math.abs(points[i][0] - points[j][0])) * Math.max(1, Math.abs(points[i][1] - points[j][1]))); for (int k = 0; k < n; ++k){ if (inRec(points[i], points[j], points[k])){ rec.add(k); } } recs.add(rec); } } int[] dp = new int[1 << n]; //所有点加入到集合中的状态总数 Arrays.fill(dp, INF); dp[0] = 0; for (int s = 0; s < 1 << n; ++s){ for (Rec rec : recs){ int ns = s | rec.covered; if (dp[s] != INF && ns != s){ dp[ns] = Math.min(dp[ns], dp[s] + rec.area); } } } out.println(dp[(1 << n) - 1]); } } void run() throws Exception { is = oj ? System.in : new FileInputStream(new File(INPUT)); out = new PrintWriter(System.out); long s = System.currentTimeMillis(); solve(); out.flush(); tr(System.currentTimeMillis() - s + "ms"); } public static void main(String[] args) throws Exception { new Main().run(); } private byte[] inbuf = new byte[1024]; public int lenbuf = 0, ptrbuf = 0; private int readByte() { if (lenbuf == -1) throw new InputMismatchException(); if (ptrbuf >= lenbuf) { ptrbuf = 0; try { lenbuf = is.read(inbuf); } catch (IOException e) { throw new InputMismatchException(); } if (lenbuf <= 0) return -1; } return inbuf[ptrbuf++]; } private boolean isSpaceChar(int c) { return !(c >= 33 && c <= 126); } private int skip() { int b; while ((b = readByte()) != -1 && isSpaceChar(b)) ; return b; } private double nd() { return Double.parseDouble(ns()); } private char nc() { return (char) skip(); } private String ns() { int b = skip(); StringBuilder sb = new StringBuilder(); while (!(isSpaceChar(b))) { // when nextLine, (isSpaceChar(b) && b != ' // ') sb.appendCodePoint(b); b = readByte(); } return sb.toString(); } private char[] ns(int n) { char[] buf = new char ; int b = skip(), p = 0; while (p < n && !(isSpaceChar(b))) { buf[p++] = (char) b; b = readByte(); } return n == p ? buf : Arrays.copyOf(buf, p); } private char[][] nm(int n, int m) { char[][] map = new char []; for (int i = 0; i < n; i++) map[i] = ns(m); return map; } private int[] na(int n) { int[] a = new int ; for (int i = 0; i < n; i++) a[i] = ni(); return a; } private int ni() { int num = 0, b; boolean minus = false; while ((b = readByte()) != -1 && !((b >= '0' && b <= '9') || b == '-')) ; if (b == '-') { minus = true; b = readByte(); } while (true) { if (b >= '0' && b <= '9') { num = num * 10 + (b - '0'); } else { return minus ? -num : num; } b = readByte(); } } private long nl() { long num = 0; int b; boolean minus = false; while ((b = readByte()) != -1 && !((b >= '0' && b <= '9') || b == '-')) ; if (b == '-') { minus = true; b = readByte(); } while (true) { if (b >= '0' && b <= '9') { num = num * 10 + (b - '0'); } else { return minus ? -num : num; } b = readByte(); } } private boolean oj = System.getProperty("ONLINE_JUDGE") != null; private void tr(Object... o) { if (!oj) System.out.println(Arrays.deepToString(o)); } }
POJ 3411: Paid Roads
我还纳闷,case中的110怎么来的,题目大意是说:从城市a到城市b的路费可以在城市c缴纳,也可以直接在城市a缴纳,这就意味着如果在城市c缴纳的路费较便宜,且之前已经抵达过城市c了,反正还是去b,直接在c把路费缴了更划算。它是一个根据状态在随意转变的图,跟所去过的顶点有关,可以用集合S来表示去过顶点的集合(状态压缩),接着找状态转移即可:
dp[S][v]: 表示在状态s下,抵达城市v的最短路径 方法:采用类似的Floyd-Warshall松弛算法
代码如下:
import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.io.PrintWriter; import java.util.ArrayList; import java.util.Arrays; import java.util.InputMismatchException; import java.util.List; public class Main{ InputStream is; PrintWriter out; String INPUT = "./data/judge/201707/3411.txt"; class Road{ int a; int b; int c; int p; int r; public Road(int a, int b, int c, int p, int r){ this.a = a; this.b = b; this.c = c; this.p = p; this.r = r; } } class Node implements Comparable<Node>{ int to; int dist; @Override public int compareTo(Node that) { return this.dist - that.dist; } } static final int INF = 1 << 29; void solve() { int N = ni(); int M = ni(); Road[] roads = new Road[M]; List<Road>[] g = new ArrayList ; for (int i = 0; i < N; ++i) g[i] = new ArrayList<Road>(); for (int i = 0; i < M; ++i){ int a = ni() - 1; int b = ni() - 1; int c = ni() - 1; int p = ni(); int r = ni(); roads[i] = new Road(a, b, c, p, r); g[a].add(roads[i]); } int[][] distance = new int[1 << N] ; for (int i = 0; i < 1 << N; ++i) Arrays.fill(distance[i], INF); distance[0][0] = 0; for (int i = 0; i < N; ++i){ for (int s = 0; s < 1 << N; ++s){ for (int v = 0; v < N; ++v){ for (Road r : g[v]){ int ns = s | 1 << r.a | 1 << r.b | 1 << r.c; int cost = 0; if ((s & (1 << r.c)) == 0) cost = r.r; else cost = Math.min(r.r, r.p); if (distance[s][v] != INF && distance[ns][r.b] > distance[s][v] + cost){ distance[ns][r.b] = distance[s][v] + cost; } } } } } int min = INF; for (int s = 0; s < 1 << N; ++s){ min = Math.min(min, distance[s][N - 1]); } if (min == INF) out.println("impossible"); else out.println(min); } void run() throws Exception { is = oj ? System.in : new FileInputStream(new File(INPUT)); out = new PrintWriter(System.out); long s = System.currentTimeMillis(); solve(); out.flush(); tr(System.currentTimeMillis() - s + "ms"); } public static void main(String[] args) throws Exception { new Main().run(); } private byte[] inbuf = new byte[1024]; public int lenbuf = 0, ptrbuf = 0; private int readByte() { if (lenbuf == -1) throw new InputMismatchException(); if (ptrbuf >= lenbuf) { ptrbuf = 0; try { lenbuf = is.read(inbuf); } catch (IOException e) { throw new InputMismatchException(); } if (lenbuf <= 0) return -1; } return inbuf[ptrbuf++]; } private boolean isSpaceChar(int c) { return !(c >= 33 && c <= 126); } private int skip() { int b; while ((b = readByte()) != -1 && isSpaceChar(b)) ; return b; } private double nd() { return Double.parseDouble(ns()); } private char nc() { return (char) skip(); } private String ns() { int b = skip(); StringBuilder sb = new StringBuilder(); while (!(isSpaceChar(b))) { // when nextLine, (isSpaceChar(b) && b != ' // ') sb.appendCodePoint(b); b = readByte(); } return sb.toString(); } private char[] ns(int n) { char[] buf = new char ; int b = skip(), p = 0; while (p < n && !(isSpaceChar(b))) { buf[p++] = (char) b; b = readByte(); } return n == p ? buf : Arrays.copyOf(buf, p); } private char[][] nm(int n, int m) { char[][] map = new char []; for (int i = 0; i < n; i++) map[i] = ns(m); return map; } private int[] na(int n) { int[] a = new int ; for (int i = 0; i < n; i++) a[i] = ni(); return a; } private int ni() { int num = 0, b; boolean minus = false; while ((b = readByte()) != -1 && !((b >= '0' && b <= '9') || b == '-')) ; if (b == '-') { minus = true; b = readByte(); } while (true) { if (b >= '0' && b <= '9') { num = num * 10 + (b - '0'); } else { return minus ? -num : num; } b = readByte(); } } private long nl() { long num = 0; int b; boolean minus = false; while ((b = readByte()) != -1 && !((b >= '0' && b <= '9') || b == '-')) ; if (b == '-') { minus = true; b = readByte(); } while (true) { if (b >= '0' && b <= '9') { num = num * 10 + (b - '0'); } else { return minus ? -num : num; } b = readByte(); } } private boolean oj = System.getProperty("ONLINE_JUDGE") != null; private void tr(Object... o) { if (!oj) System.out.println(Arrays.deepToString(o)); } }
本来就想试试这种算法没想到AC了,时间复杂度较高,因为我们知道,在所有可能的路费中都是正值,因此可以采用Dijkstra的算法来找寻最短路径,这样能避免大量不必要的更新,当然这里的技巧在于当下次一抵达相同顶点时,可以看成另一个顶点,因为状态不同,吼吼。
代码如下:
class Road{ int a; int b; int c; int p; int r; public Road(int a, int b, int c, int p, int r){ this.a = a; this.b = b; this.c = c; this.p = p; this.r = r; } } class Node implements Comparable<Node>{ int v; int dist; int S; public Node(int v, int dist, int S){ this.v = v; this.dist = dist; this.S = S; } @Override public int compareTo(Node that) { return this.dist - that.dist; } @Override public String toString() { return v + " " + dist + " " + S; } } static final int INF = 1 << 29; void solve() { int N = ni(); int M = ni(); Road[] roads = new Road[M]; List<Road>[] g = new ArrayList ; for (int i = 0; i < N; ++i) g[i] = new ArrayList<Road>(); for (int i = 0; i < M; ++i){ int a = ni() - 1; int b = ni() - 1; int c = ni() - 1; int p = ni(); int r = ni(); roads[i] = new Road(a, b, c, p, r); g[a].add(roads[i]); } boolean[][] visited = new boolean[1 << N] ; Node start = new Node(0, 0, 0); //顶点0 , 在状态0下的,最短距离为0 Queue<Node> queue = new PriorityQueue<Node>(); queue.offer(start); int ans = INF; while (!queue.isEmpty()){ Node r = queue.poll(); if (visited[r.S][r.v]) continue; if (r.v == N - 1){ ans = r.dist; break; } visited[r.S][r.v] = true; int v = r.v; for (Road edge : g[v]){ int ns = r.S | 1 << edge.c | 1 << edge.a | 1 << edge.b; int cost = 0; if ((r.S & 1 << edge.c) == 0) cost = edge.r; else cost = Math.min(edge.r, edge.p); int to = edge.b; queue.offer(new Node(to, cost + r.dist, ns)); } } if (ans == INF){ out.println("impossible"); } else{ out.println(ans); } }
POJ 1795: DNA Laboratory
此题做的辛苦,先用DFS发现策略是错误的,接着看如何使用状态压缩,可状态压缩还不够啊,后续还要还原最优DP路径,构造字符串,我就呵呵了。又是TLE,又是MLE,折腾了几个小时总算AC了,不过它的确是难得的好题,说说思路吧。思路:首先单纯的进行认为的字符串拼接,我们能够得到两条规则:
遇到带拼接的字符串包含与另一个字符串,则可以忽略被包含的字符串。
两个字符串互不包含,这就意味着字符串i + 字符串j 和字符串j + 字符串i这两种情况都要试一试。
刚开始采用了DFS,把每个字符串的头和尾都拼接试一试,后来发现一个问题,如何确定i和j的顺序呢?或者说给定字符串集合{a,b,c},如何确定a,b,c的拼接顺序?这里就需要采用状态压缩来枚举所有的拼接顺序。
好了,假如现在有了字符串a,拼接b和拼接c都要试下,于是有了{ab,ac},okay,如果有了字符串b,则a和c都要试下,于是有{ba,bc},c俺就不例举了。综上:
a : {ab,ac} b : {ba,bc} c : {ca,cb} 拼接枚举出来之后,从中又可以得出结论,{ab} 和 {ba}对于答案来说是不需要区分先后顺序的! 我们只需要输出:min{ab,ba}即可 至于是ab还是ba?题目说的很清楚,输出长度较小的,若长度一致则按字典序大小输出(小的输出)。 所以问题就转换成了如何衡量{ab}和{ba} 对于计算机而言,需要可以量化的标准,所谓的代价,该代价我们可以用字符串拼接的增量表示。 具体增量是什么就不重复了,看代码一目了然。 okay,这样我们只要记录最后拼接的字符串是谁,就可以根据代价来选择全局的最短拼接长度 所以定义: dp[S][j]: S表示如今有哪些字符串已经被拼接,且在该状态下以j结尾的最短拼接长度。 为什么需要定义j? 为了路径还原!以及为了衡量拼接代价,自行体会。 这样状态转移矩阵就跟着出来了。 dp[S | 1 << j][j] = min{dp[S | 1 << j][j], dp[S][i] + cost[i][j]}; 显然维护的是在选择了相同集合的字符串中,求以j结尾的最短拼接长度,与其他字符串的选取顺序无关哦。 如:j = c, S = {a, b, c} 则枚举的结果为: {abc,bac},只要保证c在最后即可 但我们又知道,这只是c在最后的情况,还有b,还有a呢,所以只能用状态压缩咯。 最后如何构造最短拼接的路径呢? 路径会有多条!这是肯定的,因为在拼接长度一定的情况下,可以出现字典序不同的情况,此时就需要把所有这些拼接情况遍历出来,选择字典序最小的即可。 两种办法:DFS遍历和迭代 我用了迭代: 首先把最短路径在DP数组中标注出来,采用负数的形式,这样可以忽略哪些INF值和正值,而专注于构造负数的路径。 具体看代码吧,这部分还是比较容易理解的。
代码如下:
import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.io.PrintWriter; import java.util.Arrays; import java.util.HashSet; import java.util.InputMismatchException; import java.util.Set; public class Main{ InputStream is; PrintWriter out; String INPUT = "./data/judge/201707/1795.txt"; static final int INF = 1 << 29; static final int MAX_N = 15; int[][] cost = new int[MAX_N][MAX_N]; int[][] dp = new int[1 << MAX_N][MAX_N]; void solve() { int test = ni(); for (int t = 0; t < test; ++t){ int len = ni(); String[] str = new String[len]; for (int j = 0; j < len; ++j){ str[j] = ns(); } //预处理,去重,把包含的字符串去除 for (int i = 0; i < len; ++i){ for (int j = 0; j < len; ++j){ if (i == j) continue; if (str[i].contains(str[j])){ str[j] = str[i]; } } } Set<String> set = new HashSet<String>(Arrays.asList(str)); int N = set.size(); String[] newStr = set.toArray(new String[0]); int[] lenStr = new int ; for (int i = 0; i < N; ++i){ lenStr[i] = newStr[i].length(); } //i右拼接j左 for (int i = 0; i < N; ++i){ for (int j = 0; j < N; ++j){ for (int l = 0; l < Math.min(lenStr[i], lenStr[j]); ++l){ if (newStr[i].substring(lenStr[i] - l).equals(newStr[j].substring(0, l))){ cost[i][j] = lenStr[j] - l; } } } } //进行最短距离拼接 for (int i = 0; i < 1 << N; ++i){ Arrays.fill(dp[i], INF); } //拼接i所需要的最短距离 for (int i = 0; i < N; ++i){ dp[0 | 1 << i][i] = lenStr[i]; } //遍历每种状态,对每种状态进行i和j的拼接 for (int s = 0; s < 1 << N; ++s){ for (int i = 0; i < N; ++i){ if (dp[s][i] != INF){ for (int j = 0; j < N; ++j){ if ((s & 1 << j) == 0){ dp[s | 1 << j][j] = Math.min(dp[s | 1 << j][j], dp[s][i] + cost[i][j]); } } } } } int bestLen = INF; for (int i = 0; i < N; ++i){ bestLen = Math.min(dp[(1 << N) - 1][i], bestLen); } for (int i = 0; i < N; ++i){ if (dp[(1 << N) - 1][i] == bestLen){ dp[(1 << N) - 1][i] = -dp[(1 << N) - 1][i]; } } for (int s = (1 << N) - 1; s >= 0; --s){ for (int i = 0; i < N; ++i){ if (dp[s][i] < 0){ for (int j = 0; j < N; ++j){ if (i != j && (s & (1 << j)) != 0){ if (dp[s & ~(1 << i)][j] + cost[j][i] == -dp[s][i]){ dp[s & ~(1 << i)][j] = -dp[s & ~(1 << i)][j]; } } } } } } String res = new String(new char[]{'z' + 1}); int append = 0; int last = -1; for (int i = 0; i < N; ++i){ if (dp[append | 1 << i][i] < 0){ if (res.compareTo(newStr[i]) > 0){ res = newStr[i]; last = i; } } } append |= 1 << last; for (int i = 0; i < N - 1; ++i){ String tail = new String(new char[]{'z' + 1}); int key = -1; for (int j = 0; j < N; ++j){ if ((append & 1 << j) == 0){ if (dp[append | 1 << j][j] < 0){ if (Math.abs(dp[append][last]) + cost[last][j] == Math.abs(dp[append | 1 << j][j])){ if (tail.compareTo(newStr[j].substring(lenStr[j] - cost[last][j])) > 0){ key = j; tail = newStr[j].substring(lenStr[j] - cost[last][j]); } } } } } last = key; append |= 1 << key; res += tail; } out.println("Scenario #"+(t + 1)+":"); out.println(res); out.println(); } } void run() throws Exception { is = oj ? System.in : new FileInputStream(new File(INPUT)); out = new PrintWriter(System.out); long s = System.currentTimeMillis(); solve(); out.flush(); tr(System.currentTimeMillis() - s + "ms"); } public static void main(String[] args) throws Exception { new Main().run(); } private byte[] inbuf = new byte[1024]; public int lenbuf = 0, ptrbuf = 0; private int readByte() { if (lenbuf == -1) throw new InputMismatchException(); if (ptrbuf >= lenbuf) { ptrbuf = 0; try { lenbuf = is.read(inbuf); } catch (IOException e) { throw new InputMismatchException(); } if (lenbuf <= 0) return -1; } return inbuf[ptrbuf++]; } private boolean isSpaceChar(int c) { return !(c >= 33 && c <= 126); } private int skip() { int b; while ((b = readByte()) != -1 && isSpaceChar(b)) ; return b; } private double nd() { return Double.parseDouble(ns()); } private char nc() { return (char) skip(); } private String ns() { int b = skip(); StringBuilder sb = new StringBuilder(); while (!(isSpaceChar(b))) { // when nextLine, (isSpaceChar(b) && b != ' // ') sb.appendCodePoint(b); b = readByte(); } return sb.toString(); } private char[] ns(int n) { char[] buf = new char ; int b = skip(), p = 0; while (p < n && !(isSpaceChar(b))) { buf[p++] = (char) b; b = readByte(); } return n == p ? buf : Arrays.copyOf(buf, p); } private char[][] nm(int n, int m) { char[][] map = new char []; for (int i = 0; i < n; i++) map[i] = ns(m); return map; } private int[] na(int n) { int[] a = new int ; for (int i = 0; i < n; i++) a[i] = ni(); return a; } private int ni() { int num = 0, b; boolean minus = false; while ((b = readByte()) != -1 && !((b >= '0' && b <= '9') || b == '-')) ; if (b == '-') { minus = true; b = readByte(); } while (true) { if (b >= '0' && b <= '9') { num = num * 10 + (b - '0'); } else { return minus ? -num : num; } b = readByte(); } } private long nl() { long num = 0; int b; boolean minus = false; while ((b = readByte()) != -1 && !((b >= '0' && b <= '9') || b == '-')) ; if (b == '-') { minus = true; b = readByte(); } while (true) { if (b >= '0' && b <= '9') { num = num * 10 + (b - '0'); } else { return minus ? -num : num; } b = readByte(); } } private boolean oj = System.getProperty("ONLINE_JUDGE") != null; private void tr(Object... o) { if (!oj) System.out.println(Arrays.deepToString(o)); } }
相关文章推荐
- 挑战程序竞赛系列(45):4.1Polya 计数定理(1)
- 挑战程序竞赛系列(71):4.7高度数组(1)
- 挑战程序竞赛系列(78):4.3 2-SAT(2)
- 挑战程序竞赛系列(73):4.7高度数组(3)
- 挑战程序竞赛系列(87):3.6平面扫描(1)
- 挑战程序竞赛系列(91):3.6凸包(2)
- 挑战程序竞赛系列(65):4.7字符串上的动态规划(3)
- 挑战程序竞赛系列(66):4.7字符串匹配(1)
- 挑战程序竞赛系列(31):4.5剪枝
- 挑战程序竞赛系列(14):2.6素数
- 挑战程序竞赛系列(3):2.3需要思考的动规
- 挑战程序竞赛系列(39):4.1模运算的世界(2)
- 挑战程序竞赛系列(44):4.1计数 欧拉函数
- 挑战程序竞赛系列(76):4.3强连通分量分解(3)
- 挑战程序竞赛系列(92):3.6凸包(3)
- 挑战程序竞赛系列(60):4.6树上的分治法(3)
- 挑战程序竞赛系列(10):2.4并查集
- 挑战程序竞赛系列(43):4.1矩阵 高斯消元
- 挑战程序竞赛系列(25):3.5最大权闭合图
- 挑战程序竞赛系列(83):3.6计算几何基础