1 module dkh.datastructure.convexhull; 2 3 import dkh.container.deque; 4 5 /// Convex Hull's query type 6 enum CHQueryType { 7 incr, /// 8 decr, /// 9 } 10 11 /** 12 Convex Hull Trick 13 14 Params: 15 T = value type 16 queryType = if queries are increase, use CHMode.incr. 17 if queries are decrease, use CHMode.decr. 18 */ 19 struct ConvexHull(T, CHQueryType queryType) { 20 import std.algorithm : max; 21 alias L = T[2]; /// Line type $(D y = L[0] * x + L[1]) 22 private static T value(L l, T x) { return l[0]*x + l[1]; } 23 24 Deque!L lines; 25 // can remove mid? 26 private static bool isNeed(L mid, L left, L right) { 27 assert(left[0] <= mid[0] && mid[0] <= right[0]); 28 return (right[0]-mid[0])*(left[1]-mid[1]) < (mid[0]-left[0])*(mid[1]-right[1]); 29 } 30 private void insertFront(L l) { 31 if (lines.empty) { 32 lines.insertFront(l); 33 return; 34 } 35 assert(l[0] <= lines[0][0]); 36 if (l[0] == lines[0][0]) { 37 if (l[1] <= lines[0][1]) return; 38 lines.removeFront; 39 } 40 while (lines.length >= 2 && !isNeed(lines.front, l, lines[1])) { 41 lines.removeFront; 42 } 43 lines.insertFront(l); 44 } 45 private void insertBack(L l) { 46 if (lines.empty) { 47 lines.insertBack(l); 48 return; 49 } 50 assert(lines[$-1][0] <= l[0]); 51 if (lines[$-1][0] == l[0]) { 52 if (l[1] <= lines[$-1][1]) return; 53 lines.removeBack; 54 } 55 while (lines.length >= 2 && !isNeed(lines.back, lines[$-2], l)) { 56 lines.removeBack; 57 } 58 lines.insertBack(l); 59 } 60 /** 61 Insert line 62 63 line's degree must be minimum or maximum 64 */ 65 void insertLine(L line) { 66 if (lines.empty) { 67 lines.insertBack(line); 68 return; 69 } 70 if (line[0] <= lines[0][0]) insertFront(line); 71 else if (lines[$-1][0] <= line[0]) insertBack(line); 72 else { 73 assert(false, "line's degree must be minimum or maximum"); 74 } 75 } 76 /// get maximum y 77 long maxY(T x) { 78 assert(lines.length); 79 static if (queryType == CHQueryType.incr) { 80 while (lines.length >= 2 && 81 value(lines[0], x) <= value(lines[1], x)) { 82 lines.removeFront; 83 } 84 return value(lines.front, x); 85 } else { 86 while (lines.length >= 2 && 87 value(lines[$-2], x) >= value(lines[$-1], x)) { 88 lines.removeBack; 89 } 90 return value(lines.back, x); 91 } 92 } 93 } 94 95 /// 96 unittest { 97 ConvexHull!(int, CHQueryType.incr) c; 98 c.insertLine([1, 4]); 99 c.insertLine([2, 1]); 100 c.insertLine([3, -100]); 101 assert(c.maxY(-1) == 3); // 1 * (-1) + 4 = 3 102 c.insertLine([-10, 100]); 103 assert(c.maxY(2) == 80); // 2 * (-10) + 100 = 80 104 } 105 106 unittest { 107 import std.random; 108 import std.algorithm; 109 int getMax(int[2][] v, int x) { 110 int ans = int.min; 111 foreach (l; v) { 112 ans = max(ans, l[0]*x + l[1]); 113 } 114 return ans; 115 } 116 void f1() { 117 int[2][] v = new int[2][](100); 118 int[] smp = new int[](100); 119 foreach (i; 0..100) { 120 v[i][0] = uniform(-100, 100); 121 v[i][1] = uniform(-100, 100); 122 smp[i] = uniform(-10000, 10000); 123 } 124 sort(v); 125 sort(smp); 126 ConvexHull!(int, CHQueryType.incr) c; 127 if (uniform(0, 2)) { 128 foreach_reverse (i; 0..100) { 129 c.insertLine(v[i]); 130 } 131 } else { 132 foreach (i; 0..100) { 133 c.insertLine(v[i]); 134 } 135 } 136 foreach (i; 0..100) { 137 assert(c.maxY(smp[i]) == getMax(v, smp[i])); 138 } 139 } 140 void f2() { 141 int[2][] v = new int[2][](100); 142 int[] smp = new int[](100); 143 foreach (i; 0..100) { 144 v[i][0] = uniform(-100, 100); 145 v[i][1] = uniform(-100, 100); 146 smp[i] = uniform(-10000, 10000); 147 } 148 sort(v); 149 sort(smp); reverse(smp); 150 ConvexHull!(int, CHQueryType.decr) c; 151 if (uniform(0, 2)) { 152 foreach_reverse (i; 0..100) { 153 c.insertLine(v[i]); 154 } 155 } else { 156 foreach (i; 0..100) { 157 c.insertLine(v[i]); 158 } 159 } 160 foreach (i; 0..100) { 161 assert(c.maxY(smp[i]) == getMax(v, smp[i])); 162 } 163 } 164 165 import std.stdio; 166 import dkh.stopwatch; 167 auto ti = benchmark!(f1, f2)(500); 168 writeln("ConvexHull Random500: ", ti[].map!(a => a.toMsecs())); 169 }