import java.util.Arrays;
import java.util.Random;

// https://en.wikipedia.org/wiki/R-tree
public class RTree {

	public static class Segment {
		final int x1, y1, x2, y2;

		public Segment(int x1, int y1, int x2, int y2) {
			this.x1 = x1;
			this.y1 = y1;
			this.x2 = x2;
			this.y2 = y2;
		}
	}

	final int[] x1, y1, x2, y2;
	final int[] minx, maxx, miny, maxy;

	public RTree(Segment[] segments) {
		int n = segments.length;
		x1 = new int[n];
		y1 = new int[n];
		x2 = new int[n];
		y2 = new int[n];
		minx = new int[n];
		maxx = new int[n];
		miny = new int[n];
		maxy = new int[n];
		Arrays.fill(minx, Integer.MAX_VALUE);
		Arrays.fill(maxx, Integer.MIN_VALUE);
		Arrays.fill(miny, Integer.MAX_VALUE);
		Arrays.fill(maxy, Integer.MIN_VALUE);
		build(0, n, true, segments);
	}

	void build(int low, int high, boolean divX, Segment[] segments) {
		if (low >= high)
			return;
		int mid = (low + high) >>> 1;
		nth_element(segments, low, high, mid, divX);

		x1[mid] = segments[mid].x1;
		y1[mid] = segments[mid].y1;
		x2[mid] = segments[mid].x2;
		y2[mid] = segments[mid].y2;

		for (int i = low; i < high; i++) {
			minx[mid] = Math.min(minx[mid], Math.min(segments[i].x1, segments[i].x2));
			miny[mid] = Math.min(miny[mid], Math.min(segments[i].y1, segments[i].y2));
			maxx[mid] = Math.max(maxx[mid], Math.max(segments[i].x1, segments[i].x2));
			maxy[mid] = Math.max(maxy[mid], Math.max(segments[i].y1, segments[i].y2));
		}

		build(low, mid, !divX, segments);
		build(mid + 1, high, !divX, segments);
	}

	static final Random rnd = new Random(1);

	// See: http://www.cplusplus.com/reference/algorithm/nth_element
	static void nth_element(Segment[] a, int low, int high, int n, boolean divX) {
		while (true) {
			int k = partition(a, low, high, low + rnd.nextInt(high - low), divX);
			if (n < k)
				high = k;
			else if (n > k)
				low = k + 1;
			else
				return;
		}
	}

	static int partition(Segment[] a, int fromInclusive, int toExclusive, int separatorIndex, boolean divX) {
		int i = fromInclusive;
		int j = toExclusive - 1;
		if (i >= j) return j;
		int separator = divX ? a[separatorIndex].x1 + a[separatorIndex].x2 : a[separatorIndex].y1 + a[separatorIndex].y2;
		swap(a, i++, separatorIndex);
		while (i <= j) {
			while (i <= j && (divX ? a[i].x1 + a[i].x2 : a[i].y1 + a[i].y2) < separator)
				++i;
			while (i <= j && (divX ? a[j].x1 + a[j].x2 : a[j].y1 + a[j].y2) > separator)
				--j;
			if (i >= j)
				break;
			swap(a, i++, j--);
		}
		swap(a, j, fromInclusive);
		return j;
	}

	static void swap(Segment[] a, int i, int j) {
		Segment t = a[i];
		a[i] = a[j];
		a[j] = t;
	}

	double bestDist;
	int bestNode;

	public int findNearestNeighbour(int x, int y) {
		bestDist = Double.POSITIVE_INFINITY;
		findNearestNeighbour(0, x1.length, x, y, true);
		return bestNode;
	}

	void findNearestNeighbour(int low, int high, int x, int y, boolean divX) {
		if (low >= high)
			return;
		int mid = (low + high) >>> 1;
		double distance = pointToSegmentSquaredDistance(x, y, x1[mid], y1[mid], x2[mid], y2[mid]);
		if (bestDist > distance) {
			bestDist = distance;
			bestNode = mid;
		}

		long delta = divX ? 2 * x - x1[mid] - x2[mid] : 2 * y - y1[mid] - y2[mid];

		if (delta <= 0) {
			findNearestNeighbour(low, mid, x, y, !divX);
			if (mid + 1 < high) {
				int mid1 = (mid + 1 + high) >>> 1;
				long dist = divX ? getDist(x, minx[mid1], maxx[mid1]) : getDist(y, miny[mid1], maxy[mid1]);
				if (dist * dist < bestDist)
					findNearestNeighbour(mid + 1, high, x, y, !divX);
			}
		} else {
			findNearestNeighbour(mid + 1, high, x, y, !divX);
			if (low < mid) {
				int mid1 = (low + mid) >>> 1;
				long dist = divX ? getDist(x, minx[mid1], maxx[mid1]) : getDist(y, miny[mid1], maxy[mid1]);
				if (dist * dist < bestDist)
					findNearestNeighbour(low, mid, x, y, !divX);
			}
		}
	}

	static int getDist(int v, int min, int max) {
		if (v <= min)
			return min - v;
		if (v >= max)
			return v - max;
		return 0;
	}

	static double pointToSegmentSquaredDistance(int x, int y, int x1, int y1, int x2, int y2) {
		long dx = x2 - x1;
		long dy = y2 - y1;
		long px = x - x1;
		long py = y - y1;
		long squaredLength = dx * dx + dy * dy;
		long dotProduct = dx * px + dy * py;
		if (dotProduct <= 0 || squaredLength == 0)
			return px * px + py * py;
		if (dotProduct >= squaredLength)
			return (px - dx) * (px - dx) + (py - dy) * (py - dy);
		double q = (double) dotProduct / squaredLength;
		return (px - q * dx) * (px - q * dx) + (py - q * dy) * (py - q * dy);
	}

	// random test
	public static void main(String[] args) {
		for (int step = 0; step < 100_000; step++) {
			int qx = rnd.nextInt(1000) - 500;
			int qy = rnd.nextInt(1000) - 500;
			int n = rnd.nextInt(100) + 1;
			Segment[] segments = new Segment[n];
			double minDist = Double.POSITIVE_INFINITY;
			for (int i = 0; i < n; i++) {
				int x1 = rnd.nextInt(1000) - 500;
				int y1 = rnd.nextInt(1000) - 500;
				int x2 = x1 + rnd.nextInt(10);
				int y2 = y1 + rnd.nextInt(10);
				segments[i] = new Segment(x1, y1, x2, y2);
				minDist = Math.min(minDist, pointToSegmentSquaredDistance(qx, qy, x1, y1, x2, y2));
			}
			RTree rTree = new RTree(segments);
			int index = rTree.findNearestNeighbour(qx, qy);
			Segment s = segments[index];
			if (minDist != rTree.bestDist || !(Math.abs(pointToSegmentSquaredDistance(qx, qy, s.x1, s.y1, s.x2, s.y2) - minDist) < 1e-9))
				throw new RuntimeException();
		}
	}
}
