import java.util.*; public class ConvexHull { public static Point[] convexHull(Point[] points) { Arrays.sort(points, (a, b) -> Integer.compare(a.x, b.x) != 0 ? Integer.compare(a.x, b.x) : Integer.compare(a.y, b.y)); int n = points.length; Point[] hull = new Point[n + 1]; int cnt = 0; for (int i = 0; i < 2 * n; i++) { int j = i < n ? i : 2 * n - 1 - i; while (cnt >= 2 && isNotRightTurn(hull[cnt - 2], hull[cnt - 1], points[j])) --cnt; hull[cnt++] = points[j]; } return Arrays.copyOf(hull, cnt - 1); } static boolean isNotRightTurn(Point a, Point b, Point c) { long cross = (long) (a.x - b.x) * (c.y - b.y) - (long) (a.y - b.y) * (c.x - b.x); long dot = (long) (a.x - b.x) * (c.x - b.x) + (long) (a.y - b.y) * (c.y - b.y); return cross < 0 || cross == 0 && dot <= 0; } public static class Point { public final int x, y; public Point(int x, int y) { this.x = x; this.y = y; } } public static Point[] convexHull2(Point[] p) { int n = p.length; if (n <= 1) return p; Arrays.sort(p, (a, b) -> Integer.compare(a.x, b.x) != 0 ? Integer.compare(a.x, b.x) : Integer.compare(a.y, b.y)); Point[] h = new Point[n * 2]; int cnt = 0; for (int i = 0; i < n; h[cnt++] = p[i++]) while (cnt > 1 && cross(h[cnt - 2], h[cnt - 1], p[i]) >= 0) --cnt; for (int i = n - 2, t = cnt; i >= 0; h[cnt++] = p[i--]) while (cnt > t && cross(h[cnt - 2], h[cnt - 1], p[i]) >= 0) --cnt; return Arrays.copyOf(h, cnt - 1 - (h[0].x == h[1].x && h[0].y == h[1].y ? 1 : 0)); } static long cross(Point a, Point b, Point c) { return (long) (b.x - a.x) * (c.y - a.y) - (long) (b.y - a.y) * (c.x - a.x); } // random test public static void main(String[] args) { Random rnd = new Random(1); for (int step = 0; step < 100_000; step++) { int n = rnd.nextInt(10) + 1; Point[] points = new Point[n]; for (int i = 0; i < n; i++) { int range = 10; points[i] = new Point(rnd.nextInt(range) - range / 2, rnd.nextInt(range) - range / 2); } Point[] convexHull = convexHull(points); Point[] convexHull2 = convexHull2(points); for (int i = 0; i < Math.max(convexHull.length, convexHull2.length); i++) if (convexHull[i].x != convexHull2[i].x || convexHull[i].y != convexHull2[i].y) throw new RuntimeException(); for (int i = 0; i <= convexHull.length; i++) { final Point[] hull; if (i == 0) { hull = convexHull; } else { List list = new ArrayList<>(); Collections.addAll(list, convexHull); list.remove(i - 1); hull = list.toArray(new Point[list.size()]); } boolean exterior = false; for (Point point : points) exterior |= pointInPolygon(point.x, point.y, hull) == -1; if (exterior != (i > 0)) throw new RuntimeException(); } } } static int pointInPolygon(int qx, int qy, Point[] points) { int n = points.length; int cnt = 0; for (int i = 0, j = n - 1; i < n; j = i++) { if (points[i].y == qy && (points[i].x == qx || points[j].y == qy && (points[i].x <= qx || points[j].x <= qx) && (points[i].x >= qx || points[j].x >= qx))) return 0; // boundary if ((points[i].y > qy) != (points[j].y > qy)) { long det = (long) (points[i].x - qx) * (points[j].y - qy) - (long) (points[j].x - qx) * (points[i].y - qy); if (det == 0) return 0; // boundary if ((det > 0) != (points[j].y - points[i].y > 0)) ++cnt; } } return cnt % 2 == 0 ? -1 /* exterior */ : 1 /* interior */; } }