《串并行数据结构与算法(SML语言)实验》题解
注意:本题解仅供参考学习,请勿直接抄袭代码,否则造成的后果和笔者无关。
第一题:
题意:
对n个数升序排序。
题解:
快排,不解释。
代码(省略了输入输出函数,下同):
1 val n = getInt (); 2 val l = getIntTable (n); 3 fun qsort [] = [] 4 | qsort l' = let 5 val p = hd l'; 6 val l1 = List.filter (fn x => x < p) l'; 7 val l2 = List.filter (fn x => x = p) l'; 8 val l3 = List.filter (fn x => x > p) l'; 9 in qsort(l1) @ l2 @ qsort(l3) end; 10 printIntTable (qsort l);
第二题:
题意:
单源最短路,点数1000以内,边数3000以内。
题解:
实在想不出SML语言怎么写邻接表,考虑到点数只有1000,所以直接用邻接矩阵,既然如此,优先队列优化也不带了,O(n2)水过。
代码:
1 val inf = 0x3fffffff; 2 val n = getInt() and m = getInt() and s = getInt() - 1; 3 val g = Array2.array (n, n, inf); (* 邻接矩阵 *) 4 List.tabulate (n, fn x => Array2.update (g, x, x, 0)); 5 fun read _ = let 6 val a = getInt() - 1 and b = getInt() - 1 and c = getInt(); 7 val c1 = Int.min (c, Array2.sub (g, a, b)); 8 val c2 = Int.min (c, Array2.sub (g, b, a)); 9 val t1 = Array2.update (g, a, b, c1); 10 val t2 = Array2.update (g, b, a, c2); 11 in 0 end; 12 List.tabulate (m, read); 13 val d = Array.array (n, inf); (* 其他点到源点距离 *) 14 val v = Array.array (n, false); (* 该点是否已访问 *) 15 Array.update(d, s, 0); 16 fun relax _ = let 17 val (m, u) = Array.foldli 18 (fn (i, a, (m, u)) => if (not (Array.sub (v, i))) andalso a < m then (a, i) 19 else (m, u)) 20 (inf, ~1) 21 d; (* 寻找d中最小的值 *) 22 in if u <> ~1 then let 23 val t0 = Array.update(v, u, true); 24 val t1 = Vector.foldli 25 (fn (i, a, _) => if a <> inf andalso m + a < Array.sub(d, i) then Array.update(d, i, m + a) (* 寻找能松弛的边(d[v] > d[u] + e(u, v)) *) 26 else ()) 27 () 28 (Array2.row (g, u)); 29 in 0 end else 0 end; 30 List.tabulate (n, relax); 31 val out = Array.foldr (op ::) [] d; (* Array转List *) 32 printIntTable (map (fn x => if x = inf then ~1 else x) out);
第三题:
题意:
寻找括号序列中最长的闭合字串(闭合就是满足串内所有括号匹配且最外面由括号包裹)。
题解:
维护一个栈存储当前待匹配左括号出现的位置,每当出现一个右括号且栈不为空则用这个右括号和栈顶左括号的距离更新最大值,并让栈顶出栈。不过该算法是串行的,并行我想不出来。
代码:
1 val n = getInt (); 2 val a = ListPair.zip (List.tabulate (n, fn x => x), getIntTable n); (* 将读入的List和索引zip在一起 *) 3 fun calc ((i, x), (st, m)) = (* i是当前的位置,x表示当前是左还是右括号,st是栈,m是当前的最大值 *) 4 if x = 0 then (i::st, m) else 5 if st = [] then (st, m) else let 6 val h = hd st; 7 val tm = Int.max (m, i - h + 1); 8 in (tl st, tm) end; 9 val ans = #2 (foldl calc ([], 0) a); 10 printInt ans;
第四题:
题意:
水平坐标轴上有n个楼房,每个楼房都有一个高度,且可能相互覆盖,要求求出这些楼房的轮廓线(即每一“段”的高度)。
题解:
首先将高度离散化(按照大小映射到0,1,2……n),然后将楼房的左边界和右边界一起排序,同时维护一个串,每当遇到一个边界则让该串0到边界高度(离散化后)的元素+1(左边界)或-1(右边界),并查询修改后该串最大的不是0的元素的位置对应的高度和修改前该串最大的不是0的元素的位置对应的高度比较,如果不一样则输出。对这个串的修改和查询因为是区间修改和查询,我感觉需要用线段树,事实上这道题的通用版本(不在水平坐标轴上)确实只能用线段树,但是这道题因为区间修改查询的左端点都是0,所以可能O(nlogn)还有别的方法,我想不出。不过既然出题人给我们放宽了要求,所以用O(n2)应该也能过,于是我就在遇到边界时就直接在串里边界高度(离散化后)的单个元素+1或-1,并维护一个离散化高度的最大值,更新时如果当前是右边界且-1后离散化高度的最大值会改变则往左再查找,这一步会花O(n),不过也省去了线段树大量的代码。
代码:
1 fun sort f [] = [] | sort f (h :: a) = let 2 val a1 = List.filter (fn x => f (x, h)) a; 3 val a2 = List.filter (fn x => not (f (x, h))) a; 4 in (sort f a1) @ [h] @ (sort f a2) end; (* 泛型排序函数 *) 5 val n = getInt(); 6 fun read _ = let 7 val a = getInt() and b = getInt() and c = getInt(); 8 in (a, b, c) end; 9 val reada = List.tabulate (n, read); 10 val sorta = sort (fn (x, y) => (#2 x) < (#2 y)) reada; (* 按高度排序 *) 11 val vechei = Vector.fromList (List.map (fn x => (#2 x)) sorta); (* 将高度映射到离散化的数 *) 12 val aa = ListPair.map 13 (fn (x, (y, _, z)) => (y, x, z)) 14 (List.tabulate (n, fn x => x), sorta); 15 fun f ([]) = [] | f (x :: y: (int * int * int) list) = (#1 x, #2 x, 1) :: (#3 x, #2 x, 0) :: f (y); 16 val borda = sort (fn (x, y) => (#1 x) < (#1 y)) (f aa); (* 将边界排序 *) 17 val hei = Array.array(n, 0); 18 val m = Array.array(1, ~1); 19 fun f2 (a, b, c) = 20 if c = 1 then let 21 val t1 = Array.update (hei, b, (Array.sub (hei, b)) + 1); 22 val tm = Array.sub (m, 0); 23 val t2 = if Array.sub (hei, b) = 1 then 24 if b > tm then let (* 如果左边界更新后大于原来的最大值 *) 25 val t3 = if tm = ~1 orelse 26 (Vector.sub (vechei, b)) <> (Vector.sub (vechei, tm)) then let 27 val t5 = printIntTable [a, Vector.sub (vechei, b)]; 28 val t6 = print ("\n"); 29 in () end else (); 30 val t4 = Array.update (m, 0, b); 31 in () end else () 32 else () 33 in () end else let 34 val t1 = Array.update (hei, b, (Array.sub (hei, b)) - 1); 35 val t2 = if Array.sub (hei, b) = 0 then 36 if b = Array.sub (m, 0) then let (* 如果待更新的值等于最大值(即更新后最大值会减小) *) 37 fun find ~1 = ~1 38 | find i = if (Array.sub (hei, i)) > 0 then i else find (i - 1); 39 val t3 = Array.update (m, 0, find b); 40 val tm = Array.sub (m, 0); 41 val t4 = if tm = ~1 then let 42 val t5 = printIntTable [a, 0]; 43 val t6 = print ("\n"); 44 in () end else 45 if (Vector.sub (vechei, b)) <> (Vector.sub (vechei, tm)) then let 46 val t5 = printIntTable [a, Vector.sub (vechei, tm)]; 47 val t6 = print ("\n"); 48 in () end else (); 49 in () end else () 50 else () 51 in () end; 52 List.app f2 borda;
第五题:
题意:
给定一个括号序列,判断它是否是匹配的。
题解:
把左括号看作+1,右括号看作-1,只要所有前缀和都大于0,最后总和等于0即可。这个是串行算法,我没想出并行算法怎么保证Work为n。
代码:
1 val n = getInt(); 2 val l = getIntTable n; 3 fun calc (x, (f, sum)) = 4 if not f then (f, sum) else 5 if x = 0 then (f, sum + 1) else 6 if sum = 0 then (false, sum) else (f, sum - 1); 7 val (a, b) = List.foldl calc (true, 0) l; 8 if a andalso b = 0 then printInt 1 else printInt 0;
第六题:
题意:
高精度加、减、乘,不给用IntInf。
题解:
类似小学列竖式不解释。注意要小心前导0,对操作数首先要清一次前导0,减法的结果当然要清一次,关键是乘法的结果也要清,防止因为0乘导致结果为0的情况。
代码:
1 fun clear0 [] = [0] | clear0 l = if hd l = 0 then clear0 (tl l) else l; 2 val n1 = getInt (); 3 val num1 = List.rev (clear0 (getIntTable n1)); 4 val n2 = getInt (); 5 val num2 = List.rev (clear0 (getIntTable n2)); 6 fun plus (c, (a, b, [])) = 7 ((a + c) div 10, ((a + c) mod 10) :: b, []) 8 | plus (c, (a, b, x :: l)) = 9 ((a + x + c) div 10, ((a + x + c) mod 10) :: b, l); 10 val (a1, b1, _) = List.foldl plus (0, [], num2) num1; 11 if a1 > 0 then printIntTable (a1 :: b1) else printIntTable b1; 12 printEndOfLine (); 13 14 fun minus (c, (a, b, [])) = 15 if c - a < 0 then 16 (1, (10 + c - a) :: b, []) 17 else (0, (c - a) :: b, []) 18 | minus (c, (a, b, x :: l)) = 19 if c - x - a < 0 then 20 (1, (10 + c - x - a) :: b, l) 21 else (0, (c - x - a) :: b, l); 22 val (_, b2, _) = List.foldl minus (0, [], num2) num1; 23 printIntTable (clear0 b2); 24 printEndOfLine (); 25 26 fun mul1D ((i, x), y) = let 27 fun mul2D (c, (a, b)) = ((a + c * x) div 10, ((a + c * x) mod 10) :: b); 28 val (a3, b3) = List.foldl mul2D (0, []) num1; 29 val c3 = List.rev ((if a3 > 0 then a3 :: b3 else b3) 30 @ List.tabulate (i, fn x => 0)); (* 第二个操作数第i位乘第一个操作数之后要补i个0 *) 31 val (a4, b4, _) = List.foldl plus (0, [], List.rev y) c3; 32 in if a4 > 0 then a4 :: b4 else b4 end; 33 printIntTable (clear0 (List.foldl mul1D [] 34 (ListPair.zip (List.tabulate (n2, fn x => x), num2))));
第七题:
题意:
求无向图的割点和桥(割边)。
题解:
Tarjan算法。我用Array套List实现邻接表,但是效率我有点不明,另外用大小为1的Array模拟可变变量是真的爽。
代码:
1 val n = getInt() and m = getInt(); 2 val g : (int * int) list array = Array.array (n, []); 3 fun reade i = let 4 val a = getInt() - 1 and b = getInt() - 1; 5 val t1 = Array.update(g, a, (b, i) :: (Array.sub (g, a))); 6 val t2 = Array.update(g, b, (a, i) :: (Array.sub (g, b))); 7 in 0 end; 8 List.tabulate (m, reade); 9 val cp = Array.array (n, false); (* 某点是否为割点 *) 10 val ce = Array.array (m, false); (* 某边是否为桥 *) 11 val dfn = Array.array (n, 0); 12 val low = Array.array (n, 0); 13 val cnt = Array.array (1, 1); 14 15 fun tarjan fa x = let 16 val num = Array.sub (cnt, 0); 17 val t1 = Array.update (cnt, 0, num + 1); 18 val t2 = Array.update (dfn, x, num); 19 val t3 = Array.update (low, x, num); 20 val ch = Array.array (1, 0); 21 fun calc (i, j) = 22 if (Array.sub (dfn, i)) = 0 then let 23 val t1 = Array.update (ch, 0, (Array.sub (ch, 0)) + 1); 24 val t2 = tarjan x i; 25 val t3 = Array.update (low, x, 26 Int.min(Array.sub (low, x), Array.sub (low, i))); 27 val t4 = if x = 0 andalso (Array.sub (ch, 0)) > 1 then 28 (Array.update (cp, x, true)) else (); 29 val t5 = if x <> 0 andalso (Array.sub (dfn, x)) <= (Array.sub (low, i)) then 30 (Array.update (cp, x, true)) else (); 31 val t6 = if (Array.sub (dfn, x)) < (Array.sub (low, i)) then 32 (Array.update (ce, j, true)) else (); 33 in () end else 34 if i <> fa then 35 Array.update (low, x, Int.min(Array.sub (low, x), Array.sub (dfn, i))) 36 else (); 37 val t4 = List.app calc (Array.sub (g, x)); 38 in () end; 39 40 tarjan ~1 0; 41 printInt (Array.foldl (fn (x, y) => if x then y + 1 else y) 0 cp); 42 printInt (Array.foldl (fn (x, y) => if x then y + 1 else y) 0 ce);
第八题
题意:
多次询问串中的区间最大值。串大小1000以内,询问次数5000以内。(RMQ问题)
题解:
正解当然是O(nlogn)的倍增,不过串大小1000以内,所以直接开了个O(n2)的数组,预处理所有区间的最大值,然后询问就直接在数组中查询。
代码:
1 val n = getInt() and m = getInt(); 2 val l = ListPair.zip(List.tabulate (n, (fn x => x)), getIntTable n); 3 val a = Array2.array(n, n, 0); 4 fun f i = let 5 val cl = List.drop (l, i); 6 val t1 = Array2.update(a, i, i, #2 (hd cl)); 7 val t = List.app (fn (x, y) => 8 Array2.update(a, i, x, Int.max (Array2.sub(a, i, x - 1), y))) (tl cl); 9 in 0 end; 10 List.tabulate (n, f); 11 fun q _ = let 12 val l = getInt() - 1 and r = getInt() - 1; 13 in printInt (Array2.sub(a, l, r)) end; 14 List.tabulate (m, q);
第九题:
题意:
判断质数。待判断的数非常大,差不多有40位。
题解:
朴素法T了,学习了Miller-Rabin算法,对于已知的n个素数,利用费马小定理和二次探测定理对待判断的数进行判定,判定失误的概率为4-n,复杂度大概为O(nlogp)吧,n为已知素数的个数,p为待判断的数。
代码:
1 val n = getIntInf(); 2 val t: IntInf.int list = [2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 3 61, 67, 71, 73, 79, 83, 89, 97]; 4 if (List.exists (fn x => x = n) t) then printString "True" else let 5 fun pow (a: IntInf.int, b: IntInf.int, c: IntInf.int) = 6 case b of 7 0 => 1 8 |1 => a mod c 9 |otherwise => let 10 val d = pow (a, b div 2, c); 11 in (if b mod 2 = 0 then d * d mod c else d * d * a mod c) end; 12 val pm1 = n - 1; 13 fun f1 (x: IntInf.int, cnt) = 14 if x mod 2 = 0 then (f1 (x div 2, cnt + 1)) else (x, cnt); 15 val (base, k) = f1 (pm1, 0); 16 fun f2 (test: IntInf.int, flag) = if not flag then false else let 17 val pbase = pow (test, base, n); 18 fun f3 (i, (r, aa: IntInf.int)) = 19 if not r then (r, aa) else let 20 val rnext = aa * aa mod n; 21 val rr = not (rnext = 1 andalso aa <> 1 andalso aa <> n - 1); 22 val ra = rnext; 23 in (rr, ra) end; 24 val (r, a) = List.foldl f3 (true, pbase) (List.tabulate (k, fn x => x)); 25 in r andalso a = 1 end; 26 in (if List.foldl f2 true t then printString "True" else printString "False") end;