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 }