// Liam Keliher, 2023 // Solution for NENA 2023 problem "Convex Hull Extension" (convexhullextension) // // Very case-based. // Flip/rotate each side (along with the point before and the point after) so that either: // (i) the side is horizontal on top of hull (CASE_TOP), or // (ii) the side has negative slope and is on top-right of hull (CASE_TOP_RIGHT) // If the side under consideration is labelled S(p1,p2), then the preceding and following // points are labelled p0 and p3. // Determine whether the rays R(p0,p1) and R(p3,p2): // - diverge (in which case there are infinitely many extension points) // - are parallel (in which case there are either zero or infinitely many extension points) // - converge (in which case there are finitely many extension points, so count them) // // Terminology used in code comments below: // In CASE_TOP_RIGHT, there are two ways S(p0,p1) can have negative slope (similarly for S(p2,p3)): // - negative-slope-lower: p0 is lower than p1 // - negative-slope-upper: p0 is higher than p1 import java.io.*; public class ConvexKeliher { static final int CASE_TOP = 1; static final int CASE_TOP_RIGHT = 2; static final String INFINITELY_MANY = "infinitely many"; //-------------------------------------------------------------------- public static void main(String[] args) throws IOException { BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); int n = Integer.parseInt(br.readLine()); if (n == 3) { System.out.println(INFINITELY_MANY); return; } // if int[][] points = new int[n][2]; for (int i = 0; i < n; i++) { String[] tokens = br.readLine().split(" "); int x = Integer.parseInt(tokens[0]); int y = Integer.parseInt(tokens[1]); points[i][0] = x; points[i][1] = y; } // for i // For each side S(p1,p2) of the convex hull, store the 4 points (p0,p1,p2,p3) // (counterclockwise order), and transform these points if necessary so that // the transformed side (p1,p2) is either on the top of the hull (flat) or // is on the top-right of the convex hull (==> negative slope). Record which // of these two cases applies in the cases[] array. int[][][] transformed = new int[n][4][2]; int[] cases = new int[n]; for (int i = 0; i < n; i++) { int[][] quad = new int[4][2]; quad[0][0] = points[i][0]; quad[0][1] = points[i][1]; quad[1][0] = points[(i+1)%n][0]; quad[1][1] = points[(i+1)%n][1]; quad[2][0] = points[(i+2)%n][0]; quad[2][1] = points[(i+2)%n][1]; quad[3][0] = points[(i+3)%n][0]; quad[3][1] = points[(i+3)%n][1]; cases[i] = transformIfNecessary(quad); for (int j = 0; j < 4; j++) { transformed[i][j][0] = quad[j][0]; transformed[i][j][1] = quad[j][1]; } // for j } // for i // Check for divergence for (int i = 0; i < n; i++) { if (divergent(transformed[i], cases[i])) { System.out.println(INFINITELY_MANY); return; } // if } // for i // Check for parallel lines that enclose infinitely many points for (int i = 0; i < n; i++) { if (parallelAndEncloseInfinitelyManyPoints(transformed[i], cases[i])) { System.out.println(INFINITELY_MANY); return; } // if } // for i // Everything is convergent, or parallel but enclosing zero points, so the answer is finite long answer = 0; for (int i = 0; i < n; i++) { // At this point in the program, parallel means enclosing zero points, so skip if (parallel(transformed[i])) { continue; } // if int currCase = cases[i]; if (currCase == CASE_TOP) { answer += countExtensionPointsTop(transformed[i]); } // if else { // currCase == CASE_TOP_RIGHT answer += countExtensionPointsTopRight(transformed[i]); } // else } // for i System.out.println(answer); } // main(String[]) //-------------------------------------------------------------------- // Rotate and/or reflect p0, p1, p2, p3 (if necessary) so that S(p1,p2) // is on top of hull or top-right of hull, and return CASE_TOP or // CASE_TOP_RIGHT as appropriate. When finished, points in quad[][] // (possibly transformed) are still in counterclockwise order. static int transformIfNecessary(int[][] quad) { int x0 = quad[0][0]; int y0 = quad[0][1]; int x1 = quad[1][0]; int y1 = quad[1][1]; int x2 = quad[2][0]; int y2 = quad[2][1]; int x3 = quad[3][0]; int y3 = quad[3][1]; // never used // Case 1: line segment S(p1,p2) is initially horizontal or vertical // Translate as necessary so that: // - S(p1,p2) is horizontal // - p0, p3 are below p1, p2 // - if only one of S(p0,p1), S(p2,p3) is vertical, then it is S(p0,p1) // Subcase 1-A: S(p1,p2) is initially horizontal at top of hull if (y1 == y2 && y0 < y1) { if (x0 != x1 && x2 == x3) { swapLeftToRight(quad); } // if return CASE_TOP; } // if // Subcase 1-B: S(p1,p2) is initially horizontal at bottom of hull else if (y1 == y2 && y0 > y1) { swapBottomToTop(quad); if (x0 != x1 && x2 == x3) { swapLeftToRight(quad); } // if return CASE_TOP; } // else if // Subcase 1-C: S(p1,p2) is initially vertical on right side of hull else if (x1 == x2 && x0 < x1) { rotateCounterclockwise90Degrees(quad); if (x0 != x1 && x2 == x3) { swapLeftToRight(quad); } // if return CASE_TOP; } // else if // Subcase 1-D: S(p1,p2) is initially vertical on left side of hull else if (x1 == x2 && x0 > x1) { rotateClockwise90Degrees(quad); if (x0 != x1 && x2 == x3) { swapLeftToRight(quad); } // if return CASE_TOP; } // else if // Case 2: line segment S(p1,p2) is *not* initially horizontal or vertical // Translate as necessary so that: // - S(p1,p2) is has negative slope and is positioned as if in top-right part of hull // - this means that p0 and p3 both lie strictly to the left of line L(p1,p2) // Subcase 2-A: S(p1,p2) intially in top-left of hull else if (x1 > x2 && y1 > y2) { rotateClockwise90Degrees(quad); return CASE_TOP_RIGHT; } // else if // Subcase 2-B: S(p1,p2) intially in bottom-left of hull else if (x1 < x2 && y1 > y2) { rotateClockwise90Degrees(quad); rotateClockwise90Degrees(quad); return CASE_TOP_RIGHT; } // else if // Subcase 2-C: S(p1,p2) intially in bottom-right of hull else if (x1 < x2 && y1 < y2) { rotateCounterclockwise90Degrees(quad); return CASE_TOP_RIGHT; } // else if // Subcase 2-D: S(p1,p2) initially in top-right of hull // (all is right with the world) return CASE_TOP_RIGHT; } // transformIfNecessary(int[][]) //-------------------------------------------------------------------- // Assumption: incoming points are in counterclockwise order // - after transformation, they are still in counterclockwise order static void swapLeftToRight(int[][] quad) { // negate the x-values quad[0][0] = -quad[0][0]; quad[1][0] = -quad[1][0]; quad[2][0] = -quad[2][0]; quad[3][0] = -quad[3][0]; // swap p0,p3 and p1,p2 int[] temp = quad[0]; quad[0] = quad[3]; quad[3] = temp; temp = quad[1]; quad[1] = quad[2]; quad[2] = temp; } // swapLeftToRight(int[][]) //-------------------------------------------------------------------- // Assumption: incoming points are in counterclockwise order // - after transformation, they are still in counterclockwise order // - could also be accomplished with two 90-degree rotations static void swapBottomToTop(int[][] quad) { // negate the y-values quad[0][1] = -quad[0][1]; quad[1][1] = -quad[1][1]; quad[2][1] = -quad[2][1]; quad[3][1] = -quad[3][1]; // negate the x-values quad[0][0] = -quad[0][0]; quad[1][0] = -quad[1][0]; quad[2][0] = -quad[2][0]; quad[3][0] = -quad[3][0]; } // swapBottomToTop(int[][]) //-------------------------------------------------------------------- // (x,y) -> (-y,x) static void rotateCounterclockwise90Degrees(int[][] quad) { for (int i = 0; i < 4; i++) { int x = quad[i][0]; int y = quad[i][1]; quad[i][0] = -y; quad[i][1] = x; } // for i } // rotateCounterclockwise90Degrees(int[][]) //-------------------------------------------------------------------- // (x,y) -> (y,-x) static void rotateClockwise90Degrees(int[][] quad) { for (int i = 0; i < 4; i++) { int x = quad[i][0]; int y = quad[i][1]; quad[i][0] = y; quad[i][1] = -x; } // for i } // rotateClockwise90Degrees(int[][]) //-------------------------------------------------------------------- // Returns true if rays R(p0,p1) and R(p3,p2) are divergent, and false otherwise // - parallel rays are handled separately (by parallelAndEncloseInfinitelyManyPoints()) static boolean divergent(int[][] quad, int currCase) { int x0 = quad[0][0]; int y0 = quad[0][1]; int x1 = quad[1][0]; int y1 = quad[1][1]; int x2 = quad[2][0]; int y2 = quad[2][1]; int x3 = quad[3][0]; int y3 = quad[3][1]; long[] slope01 = getSlope(x0, y0, x1, y1); long rise01 = slope01[0]; long run01 = slope01[1]; long[] slope23 = getSlope(x2, y2, x3, y3); long rise23 = slope23[0]; long run23 = slope23[1]; // NOTE: Slope is negative iff rise < 0. On the other hand, slope >= 0 requires // rise >= 0 *and* run != 0 (since a vertical slope is indicated by [1,0]) if (currCase == CASE_TOP) { // Subcase A: S(p0,p1) has negative slope if (rise01 < 0) { // Sub-subcase: S(p2,p3) has negative slope if (rise23 < 0 && rise01*run23 < rise23*run01) { // slope01 < slope23 (but larger in abs value) return true; } // else if } // if // Subcase B: S(p0,p1) is vertical else if (run01 == 0) { // Sub-subcase: S(p2,p3) has negative slope if (rise23 < 0) { return true; } // if } // if // Subcase C: S(p0,p1) has positive slope else if (run01 != 0 && rise01 > 0) { // Sub-subcase(s): S(p2,p3) is vertical or has negative slope if (run23 == 0 || rise23 < 0) { return true; } // if // Sub-subcase: S(p2,p3) has positive slope else if (run23 != 0 && rise23 > 0 && rise01*run23 < rise23*run01) { // slope01 < slope23 return true; } // else if } // else if } // if if (currCase == CASE_TOP_RIGHT) { // Subcase A: S(p0,p1) is negative-slope-lower if (rise01 < 0 && y0 < y1) { // Sub-subcase: S(p2,p3) is negative-slope-lower if (rise23 < 0 && y3 < y2 && rise01*run23 < rise23*run01) { // slope01 < slope23 (but larger in abs value) return true; } // if } // if // Subcase B: S(p0,p1) is vertical else if (run01 == 0) { // Sub-subcase: S(p2,p3) is negative-slope-lower if (rise23 < 0 && y3 < y2) { return true; } // if } // else if // Subcase C: S(p0,p1) has slope >= 0 else if (run01 != 0 && rise01 >= 0) { // Sub-subcase(s): S(p2,p3) is vertical or negative-slope-lower if (run23 == 0 || (rise23 < 0 && y3 < y2)) { return true; } // if // Sub-subcase: S(p2,p3) has positive slope (not 0) else if (rise23 > 0 && rise01*run23 < rise23*run01) { // slope01 < slope23 return true; } // else if } // else if // Subcase D: S(p0,p1) is negative-slope-upper else if (rise01 < 0 && y0 > y1) { // Sub-subcase(s): S(p2,p3) is vertical or negative-slope-lower or has slope >= 0 if (run23 == 0 || (rise23 < 0 && y3 < y2) || (run23 != 0 && rise23 >= 0)) { return true; } // if // Sub-subcase: S(p2,p3) is negative-slope-upper if (rise23 < 0 && y3 > y2 && rise01*run23 < rise23*run01) { // slope01 < slope23 (but larger in abs value) return true; } // if } // else if } // if return false; } // divergent(int[][],int) //-------------------------------------------------------------------- // Returns true if rays R(p0,p1) and R(p3,p2) are parallel, and false otherwise static boolean parallel(int[][] quad) { int x0 = quad[0][0]; int y0 = quad[0][1]; int x1 = quad[1][0]; int y1 = quad[1][1]; int x2 = quad[2][0]; int y2 = quad[2][1]; int x3 = quad[3][0]; int y3 = quad[3][1]; long[] slope01 = getSlope(x0, y0, x1, y1); long[] slope23 = getSlope(x2, y2, x3, y3); long rise01 = slope01[0]; long run01 = slope01[1]; long rise23 = slope23[0]; long run23 = slope23[1]; return rise01 == rise23 && run01 == run23; } // parallel(int[][]) //-------------------------------------------------------------------- // Returns true if rays R(p0,p1) and R(p3,p2) are parallel and enclose // infinitely many points, and false otherwise // - if parallel and don't enclose infinitely many points, then enclose zero points static boolean parallelAndEncloseInfinitelyManyPoints(int[][] quad, int currCase) { int x0 = quad[0][0]; int y0 = quad[0][1]; int x1 = quad[1][0]; int y1 = quad[1][1]; int x2 = quad[2][0]; int y2 = quad[2][1]; int x3 = quad[3][0]; int y3 = quad[3][1]; long[] slope01 = getSlope(x0, y0, x1, y1); long[] slope23 = getSlope(x2, y2, x3, y3); long rise01 = slope01[0]; long run01 = slope01[1]; long rise23 = slope23[0]; long run23 = slope23[1]; // Return false if rays are not parallel if (rise01 != rise23 || run01 != run23) { return false; } // if if (currCase == CASE_TOP) { // Subcase A: rays are distance >= 2 apart horizontally if (x1 - x2 >= 2) { return true; } // if // Subcase B: rays are distance 1 apart (horizontally) // The only way for the rays *not* to enclose infinitely many points is if: // (a) slope = +/- 1/m, where m is a positive integer // or // (b) the rays are vertical, in which case rise = 1 // (since we use [rise, run] = [1, 0] for a vertical slope) else { return !(rise01 == 1 || rise01 == -1); } // else } // if else { // currCase == CASE_TOP_RIGHT // Subcase A: rays are vertical // Rays enclose infinitely many points iff they are distance >= 2 apart horizontally if (run01 == 0) { return x1 - x2 >= 2; } // if // Subcase B: rays are horizontal // Rays enclose infinitely many points iff they are distance >= 2 apart vertically else if (rise01 == 0) { return y2 - y1 >= 2; } // else if // Subcase C: rays are neither vertical nor horizontal else { // Find x-coord of point p where R(p0,p1) has y value = y2 long pNumer = run01*(y2 - y1) + rise01*x1; long pDenom = rise01; // If negative-slope-lower or negative-slope-upper, then pDenom = rise01 < 0 if (pDenom < 0) { pNumer = -pNumer; pDenom = -pDenom; // now pDenom > 0 } // if // Doesn't hurt to reduce pNumer/pDenom long g = gcd(Math.abs(pNumer), pDenom); pNumer /= g; pDenom /= g; long gapNumer = pNumer - x2*pDenom; long gapDenom = pDenom; // If negative-slope-upper, then gapNumer < 0 if (rise01 < 0 && y0 > y1) { gapNumer = -gapNumer; } // if g = gcd(gapNumer, gapDenom); gapNumer /= g; gapDenom /= g; return processParallelRays(gapNumer, gapDenom, rise01, run01); } // else } // else } // parallelAndEncloseInfinitelyManyPoints(int[][],int) //-------------------------------------------------------------------- // Returns true if the two parallel rays specified by the input enclose // infinitely many points, and false otherwise // - one ray is anchored at (0, 0), the other at (gapNumer/gapDenom, 0) // - gapNumer/gapDenom > 0 (with gapNume > 0 and gapDenom > 0) // - slope of both rays is rise/run // - incoming slope is positive or negative (never 0 or infinite), so run > 0 // - WLOG, if the slope is negative, we make it positive static boolean processParallelRays(long gapNumer, long gapDenom, long rise, long run) { if (gapNumer <= 0 || gapDenom <= 0 || rise == 0 || run <= 0) { throw new IllegalArgumentException("Inside processParallelRays(): at least one argument is <= 0"); } // if // First, ensure slope is positive (WLOG) rise = Math.abs(rise); // Case 1: gapNumer/gapDenom > 1 // - rays enclose infinitely many points if (gapNumer > gapDenom) { return true; } // if // Case 2: gapNumer/gapDenom = 1 // - rays enclose infinitely many points iff slope is NOT equal to +/- 1/m, where m is a positive integer else if (gapNumer == gapDenom) { return !(rise == 1 || rise == -1); } // else if // Case 3: gapNumer/gapDenom < 1 // - follow left ray and right ray upward until left ray is again at a point with integer coords // - at each integer y value, check whether an integer x value lies between the rays // - if this ever happens, then the rays enclose infinitely many points; otherwise they don't else { // gapNumer < gapDenom int yUpper = (int)Math.abs(rise); for (int y = 1; y < yUpper; y++) { long xLeftNumer = run*y; long xLeftDenom = rise; // If xLeftNumer/xLeftDenom is an integer, add 1; if not, round up, so also add 1 long xLeftBumpUp = xLeftNumer/xLeftDenom + 1; long xRightNumer = gapNumer*rise + gapDenom*run*y; long xRightDenom = gapDenom*rise; long xRightBumpDown = xRightNumer/xRightDenom; if (xRightNumer % xRightDenom == 0) { // xRightNumer/xRightDenom is an integer xRightBumpDown--; } // if if (xLeftBumpUp <= xRightBumpDown) { return true; } // if } // for y return false; } // else } // processParallelRays(long,long,long,long) //-------------------------------------------------------------------- // Count the number of extension points when S(p1,p2) is horizontal // - only called when rays R(p0,p1) and R(p3,p2) are convergent static long countExtensionPointsTop(int[][] quad) { int x0 = quad[0][0]; int y0 = quad[0][1]; int x1 = quad[1][0]; int y1 = quad[1][1]; int x2 = quad[2][0]; int y2 = quad[2][1]; int x3 = quad[3][0]; int y3 = quad[3][1]; long[] slope01 = getSlope(x0, y0, x1, y1); long rise01 = slope01[0]; long run01 = slope01[1]; long[] slope23 = getSlope(x2, y2, x3, y3); long rise23 = slope23[0]; long run23 = slope23[1]; long numExtPoints = 0; long[] yCoord = yCoordOfIntersection(rise23, run23, x3, y3, rise01, run01, x0, y0); long yMeetNumer = yCoord[0]; long yMeetDenom = yCoord[1]; long yMeet; if (yMeetNumer % yMeetDenom == 0) { // equivalently, yMeetDenom = 1 yMeet = yMeetNumer/yMeetDenom - 1; } // if else { // yMeetNumer/yMeetDenom is not an integer yMeet = yMeetNumer/yMeetDenom; // rounds toward 0 if (yMeetNumer < 0) { yMeet--; // compensate for the fact that / rounded up (we want to round down here) } // if } // else for (long y = y1 + 1; y <= yMeet; y++) { // NOTE: start at y1 + 1 numExtPoints += countPointsBetween(y, rise23, run23, x3, y3, rise01, run01, x0, y0); } // for y return numExtPoints; } // countExtensionPointsTop(int[][]) //-------------------------------------------------------------------- // Count the number of extension points when S(p1,p2) is on top-right of hull // - only called when rays R(p0,p1) and R(p3,p2) are convergent static long countExtensionPointsTopRight(int[][] quad) { int x0 = quad[0][0]; int y0 = quad[0][1]; int x1 = quad[1][0]; int y1 = quad[1][1]; int x2 = quad[2][0]; int y2 = quad[2][1]; int x3 = quad[3][0]; int y3 = quad[3][1]; long[] slope01 = getSlope(x0, y0, x1, y1); long rise01 = slope01[0]; long run01 = slope01[1]; long[] slope23 = getSlope(x2, y2, x3, y3); long rise23 = slope23[0]; long run23 = slope23[1]; long[] slope12 = getSlope(x1, y1, x2, y2); long rise12 = slope12[0]; long run12 = slope12[1]; // NOTE: Slope is negative iff rise < 0. On the other hand, slope >= 0 requires // rise >= 0 *and* run != 0 (since a vertical slope is indicated by [1,0]) long numExtPoints = 0; long[] yCoord = yCoordOfIntersection(rise23, run23, x3, y3, rise01, run01, x0, y0); long yMeetNumer = yCoord[0]; long yMeetDenom = yCoord[1]; long yMeetFloor = yMeetNumer/yMeetDenom; // rounds toward 0 boolean yMeetIsInteger = yMeetNumer % yMeetDenom == 0; if (yMeetNumer < 0 && !yMeetIsInteger) { // yMeet is negative and not an integer yMeetFloor--; // compensate for the fact that / rounded up (we want to round down here) } // if // Subcase A: S(p0,p1) is negative-slope-lower or vertical or has positive slope if ((rise01 < 0 && y0 < y1) || (run01 == 0) || (rise01 > 0)) { // Sub-subcase(s): S(p2,p3) is negative-slope-upper or horizontal (so y1 < yMeet <= y2) if ((rise23 < 0 && y3 > y2) || rise23 == 0) { // If S(p2,p3) is horizontal, then yMeet = y2, so want yMeetFloor = y2 - 1 if (rise23 == 0) { yMeetFloor--; } // if // - lower part lies between S(p1,p2) on left and R(p0,p1) on right for (long y = y1 + 1; y <= yMeetFloor; y++) { // NOTE: start at y1 + 1 numExtPoints += countPointsBetween(y, rise12, run12, x1, y1, rise01, run01, x0, y0); } // for y // - upper part lies between S(p1,p2) on left and R(p3,p2) on right for (long y = yMeetFloor + 1; y < y2; y++) { // NOTE: < y2 numExtPoints += countPointsBetween(y, rise12, run12, x1, y1, rise23, run23, x3, y3); } // for y } // if // Sub-subcase: S(p2,p3) is negative-slope-lower or has positive slope (so yMeet > y2) // - S(p2,p3) cannot be vertical, since if only one of S(p0,p1), S(p2,p3) is vertical, it is always S(p0,p1) (see transformIfNecessary()) // - if S(p2,p3) is negative-slopel-lower, then S(p0,p1) must also be, since R(p0,p1) and R(p3,p2) are convergent else if ((rise23 < 0 && y3 < y2) || (run23 != 0 && rise23 > 0)) { // don't actually need to check run23 !== 0, since S(p2,p3) shouldn't be vertical here // - lower part lies between S(p1,p2) on left and R(p0,p1) on right for (long y = y1 + 1; y <= y2; y++) { // NOTE: start at y1 + 1 numExtPoints += countPointsBetween(y, rise12, run12, x1, y1, rise01, run01, x0, y0); } // for y // - upper part lies between R(p3,p2) on left and R(p0,p1) on right long upperLimit = yMeetFloor; if (yMeetIsInteger) { upperLimit--; } // if for (long y = y2 + 1; y <= upperLimit; y++) { // NOTE: start at y2 + 1 numExtPoints += countPointsBetween(y, rise23, run23, x3, y3, rise01, run01, x0, y0); } // for y } // else if } // if // Subcase B: S(p0,p1) is horizontal else if (rise01 == 0) { // Sub-subcase: S(p2,p3) must be negative-slope-upper, since R(p0,p1) and R(p3,p2) are convergent // - points of interest lie between S(p1,p2) on left and R(p3,p2) on right for (long y = y1 + 1; y < y2; y++) { // NOTE: start at y1 + 1, stop before y2 numExtPoints += countPointsBetween(y, rise12, run12, x1, y1, rise23, run23, x3, y3); } // for y } // else if // Subcase C: S(p0,p1) is negative-slope-upper else if (rise01 < 0 && y0 > y1) { // Sub-subcase: S(p2,p3) must be negative-slope-upper, since R(p0,p1) and R(p3,p2) are convergent // - upper part lies between S(p1,p2) on left and R(p3,p2) on right for (long y = y1; y < y2; y++) { // NOTE: include y1, exclude y2 numExtPoints += countPointsBetween(y, rise12, run12, x1, y1, rise23, run23, x3, y3); } // for y // - lower part lies between R(p0,p1) on left and R(p3,p2) on right long lowerLimit = yMeetFloor; for (long y = lowerLimit + 1; y < y1; y++) { // NOTE: start at lowerLimit + 1, exclue y1 (already included immediately above) numExtPoints += countPointsBetween(y, rise12, run12, x1, y1, rise23, run23, x3, y3); } // for y } // else if return numExtPoints; } // countExtensionPointsTopRight(int[][]) //-------------------------------------------------------------------- // Count points with integer coordinates that lie *strictly* between the left line and // the right line at height y // - handles cases in which one of the two lines is vertical: // - if, e.g., left line is vertical, then riseL = 1, runL = 0 // - so xLNumer = 0 + riseL*xL and xLDenom = riseL, so xLNumer/xLDenom = xL (correct) // Assumption: neither line is horizontal (so riseL > 0 and riseR > 0) static long countPointsBetween(long y, long riseL, long runL, int xL, int yL, long riseR, long runR, int xR, int yR) { long numPoints = 0; long xLNumer = runL*(y - yL) + riseL*xL; long xLDenom = riseL; if (xLDenom < 0) { xLNumer = -xLNumer; xLDenom = -xLDenom; } // if long xRNumer = runR*(y - yR) + riseR*xR; long xRDenom = riseR; if (xRDenom < 0) { xRNumer = -xRNumer; xRDenom = -xRDenom; } // if boolean leftIsInteger = (xLNumer % xLDenom == 0); boolean rightIsInteger = (xRNumer % xRDenom == 0); long widthTrunc = (xLDenom*xRNumer - xLNumer*xRDenom)/(xLDenom*xRDenom); boolean widthIsInteger = (xLDenom*xRNumer - xLNumer*xRDenom) % (xLDenom*xRDenom) == 0; // In theory this should never happen if (widthIsInteger && widthTrunc == 0) { return 0; } // if if (widthIsInteger) { if (leftIsInteger && rightIsInteger) { // would suffice to check one of leftIsInteger, rightIsInteger numPoints = widthTrunc - 1; } // if else { // neither endpoint is an integer numPoints = widthTrunc; } // else } // if else { // width is not an integer if (leftIsInteger || rightIsInteger) { // at most one endpoint can be an integer numPoints = widthTrunc; } // if else { // neither endpoint is an integer // ceiling the left endpoint long xLeftCeil = xLNumer/xLDenom; if (xLNumer > 0) { // left endpoint is positive xLeftCeil++; // compensate for fact that / rounded down (toward 0) } // if // floor the right endpoint long xRightFloor = xRNumer/xRDenom; if (xRNumer < 0) { // right endpoint is negative xRightFloor--; // compensate for the fact that / rounded up (toward 0) } // if numPoints = xRightFloor - xLeftCeil + 1; } // else } // else return numPoints; } // countPointsBetween(long,long,long,int,int,long,long,int,int) //-------------------------------------------------------------------- // Returns y-coord of intersection between the two lines as a fraction [numer, denom] // in reduced form, with denom > 0 // - handles cases in which one of the lines is vertical // - lines cannot be parallel static long[] yCoordOfIntersection(long riseL, long runL, int xL, int yL, long riseR, long runR, int xR, int yR) { long yMeetNumer; long yMeetDenom; // Case 1: neither line is vertical if (runL != 0 && runR != 0) { yMeetNumer = riseR*runR*runL*yL + riseR*riseL*runR*(xR - xL) - riseL*runR*runR*yR; yMeetDenom = runR*(riseR*runL - riseL*runR); } // if // Case 2: right line is vertical else if (runR == 0) { yMeetNumer = riseL*xR + runL*yL - riseL*xL; yMeetDenom = runL; } // else if // Case 3: left line is vertical else { // runL == 0 yMeetNumer = riseR*xL + runR*yR - riseR*xR; yMeetDenom = runR; } // else if (yMeetDenom < 0) { yMeetNumer = -yMeetNumer; yMeetDenom = -yMeetDenom; } // if long g = gcd(Math.abs(yMeetNumer), yMeetDenom); yMeetNumer /= g; yMeetDenom /= g; return new long[]{yMeetNumer, yMeetDenom}; } // yCoordOfIntersection(long,long,int,int,long,long,int,int) //-------------------------------------------------------------------- // Returns a length-2 array containing the slope of the line through // (x1,y2) and (x2,y2) (in the form [rise, run]). The slope is a fraction, // rise/run, in reduced form, with the convention that the denominator // is never negative. A vertical line is assigned slope 1/0 (never -1/0). // - Assumption: (x1,y1) != (x2,y2) (in which case slope is undefined) // - returns an array of long to help with any overflow avoidance later static long[] getSlope(int x1, int y1, int x2, int y2) { long rise = y2 - y1; long run = x2 - x1; if (run == 0) { // special case: x1 == x2 return new long[]{1, 0}; } // if else { if (run < 0) { // convention: run should always be positive rise = -rise; run = -run; } // if long g = gcd(Math.abs(rise), run); rise /= g; run /= g; return new long[]{rise, run}; } // else } // getSlope(int,int,int,int) //-------------------------------------------------------------------- // Assumption: a,b >= 0, not both 0 static long gcd(long a, long b) { if (a < 0 || b < 0 || (a == 0 && b == 0)) { throw new IllegalArgumentException("gcd() called with invalid arguments."); } // if while (b != 0) { long rem = a % b; a = b; b = rem; } // while return a; } // gcd(long,long) //-------------------------------------------------------------------- static void printTransformed(int[][] quad) { System.out.print("p0 = (" + quad[0][0] + "," + quad[0][1] + ") "); System.out.print("p1 = (" + quad[1][0] + "," + quad[1][1] + ") "); System.out.print("p2 = (" + quad[2][0] + "," + quad[2][1] + ") "); System.out.println("p3 = (" + quad[3][0] + "," + quad[3][1] + ")"); } // printTransformed(int[]) //-------------------------------------------------------------------- } // class ConvexKeliher