Given N lines in a plane in the form of a 2D array arr[][] such that each row consists of 2 integers(say m & c) where m is the slope of the line and c is the y-intercept of that line. You are given Q queries each consist of x-coordinates. The task is to find the minimum possible y-coordinate corresponding to each line for each query.
Examples:
Input: arr[][] = { { 4, 0 }, { -3, 0 }, { 5, 1 }, { 3, -1 },{ 2, 3 }, { 1, 4 } } and Q[] = {-6, 3, 100}
Output:
-29
-9
-300
Explanation:
The minimum value for x = -6 from the given set of lines is -29.
The minimum value for x = 3 from the given set of lines is -9.
The minimum value for x = -100 from the given set of lines is -300.
Naive Approach: The naive approach is to find the y-coordinates for each line and the minimum of all the y-coordinates will give the minimum y-coordinate value. Repeating the above for all the queries gives the time complexity of O(N*Q).
Efficient Approach:
Observations
- L1 an L2 are two lines and they intersect at (x1, y1), if L1 is lower than before x = x1 then L2 will be lower than L1 after x = x1. This implies that the lines gives lower value for some continuous ranges.
- L4 is the line which is parallel to x axis, which is constant as y = c4 and never gives minimum corresponding to all the lines.
- Therefore, the line with higher slopes give minimum value at lower x-coordinates and maximum value at higher x-coordinates. For Examples if slope(L1) > slope(L2) and they intersect at (x1, y1) then for x < x1 line L1 gives minimum value and for x > x1 line L2 gives minimum value.
- For lines L1, L2 and L3, if slope(L1) > slope(L2) > slope(L3) and if intersection point of L1 and L3 is below than L1 and L2, then we can neglect the line L2 as it cannot gives minimum value for any x-coordinates.
On the basis of the above observations following are the steps:
- Sort the slopes in decreasing order of slope.
- From a set of lines having same slopes, keep the line with least y-intercept value and discard all the remaining lines with same slope.
- Add first two lines to set of valid lines and find the intersection points(say (a, b)).
- For the next set of remaining lines do the following:
- Find the intersection point(say (c, d)) of the second last line and the current line.
- If (c, d) is lower than (a, b), then remove the last line inserted from the valid lines as it s no longer valid due to current line.
- Repeat the above steps to generate all the valid lines set.
- Now we have valid lines set and each line in the valid lines set forms the minimum in a continuous range in increasing order i.e., L1 is minimum in range [a, b] and L2 in range [b, c].
- Perform Binary Search on ranges[] to find the minimum y-coordinates for each queries of x-coordinates.
Below is the implementation of the above approach:
CPP
// C++ program for the above approach #include <bits/stdc++.h> using namespace std; // To store the valid lines vector<pair< int , int > > lines; // To store the distinct lines vector<pair< int , int > > distinct; // To store the ranges of intersection // points vector<pair< int , int > > ranges; // Function that returns the intersection // points pair< int , int > intersection(pair< int , int > a, pair< int , int > b) { int x = a.second - b.second; int y = b.first - a.first; return { x, y }; } // Function to see if a line is eligible // or not. // L3 is the current line being added and // we check eligibility of L2 bool isleft(pair< int , int > l1, pair< int , int > l2, pair< int , int > l3) { pair< int , int > x1, x2; // Find intersections x1 = intersection(l1, l3); x2 = intersection(l1, l2); // Returns true if x1 is left of x2 return (x1.first * x2.second < x2.first * x1.second); } // Comparator function to sort the line[] bool cmp(pair< int , int > a, pair< int , int > b) { if (a.first != b.first) return a.first > b.first; else return a.second < b.second; } // Find x-coordinate of intersection // of 2 lines int xintersect(pair< int , int > a, pair< int , int > b) { int A = a.second - b.second; int B = b.first - a.first; // Find the x coordinate int x = A / B; if (A * B < 0) x -= 1; return x; } // Function to returns the minimum // possible value for y int findy(vector<pair< int , int > >& ranges, int pt) { int lo = 0, hi = ranges.size() - 1; int mid = 0; // Binary search to find the minimum // value while (lo <= hi) { // Find middle element mid = (lo + hi) / 2; if (ranges[mid].first <= pt && ranges[mid].second >= pt) { break ; } else if (ranges[mid].first > pt) { hi = mid - 1; } else { lo = mid + 1; } } // Returns the minimum value return lines[mid].first * pt + lines[mid].second; } // Function to add a valid line and // remove the invalid lines void add(pair< int , int > x) { // Add the current line lines.push_back(x); // While Loop while (lines.size() >= 3 && isleft(lines[lines.size() - 3], lines[lines.size() - 2], lines[lines.size() - 1])) { // Erase invalid lines lines.erase(lines.end() - 2); } } // Function to updateLines on the // basis of distinct slopes void updateLines(pair< int , int > line[], int n) { // Sort the line according to // decreasing order of slope sort(line, line + n, cmp); // To track for last slope int lastslope = INT_MIN; // Traverse the line[] and find // set of distinct lines for ( int i = 0; i < n; i++) { if (line[i].first == lastslope) continue ; // Push the current line in // array distinct[] distinct.push_back(line[i]); // Update the last slope lastslope = line[i].first; } // Traverse the distinct[] and // update the valid lines to lines[] for ( int i = 0; i < distinct.size(); i++) add(distinct[i]); int left = INT_MIN; int i, right = 0; // Traverse the valid lines array for (i = 0; i < lines.size() - 1; i++) { // Find the intersection point int right = xintersect(lines[i], lines[i + 1]); // Insert the current intersection // points in ranges[] ranges.push_back({ left, right }); left = right + 1; } ranges.push_back({ left, INT_MAX }); } // Driver Code int main() { int n = 6; // Set of lines of slopes and y intercept pair< int , int > line[] = { { 4, 0 }, { -3, 0 }, { 5, 1 }, { 3, -1 }, { 2, 3 }, { 1, 4 } }; // Function Call updateLines(line, n); // Queries for x-coordinates int Q[] = { -6, 3, 100 }; // Traverse Queries to find minimum // y-coordinates for ( int i = 0; i < 3; i++) { // Use Binary Search in ranges // to find the minimum y-coordinates cout << findy(ranges, Q[i]) << endl; } return 0; } |
Java
// Java code addition import java.util.*; import java.io.*; public class ConvexHullTrick { static int INT_MAX = 2147483647 ; static int INT_MIN = - 2147483647 ; // To store the valid lines static ArrayList< int []> lines = new ArrayList<>(); // To store the distinct lines static ArrayList< int []> distinct = new ArrayList<>(); // To store the ranges of intersection // points static ArrayList< int []> ranges = new ArrayList<>(); // Function that returns the intersection // points public static int [] intersection( int [] a, int [] b) { int x = a[ 1 ] - b[ 1 ]; int y = b[ 0 ] - a[ 0 ]; return new int [] {x, y}; } // Function to see if a line is eligible // or not. // L3 is the current line being added and // we check eligibility of L2 public static boolean isLeft( int [] l1, int [] l2, int [] l3) { // Find intersections int [] x1 = intersection(l1, l3); int [] x2 = intersection(l1, l2); // Returns true if x1 is left of x2 return ((x1[ 0 ] * x2[ 1 ]) < (x2[ 0 ] * x1[ 1 ])); } // Find x-coordinate of intersection // of 2 lines public static int xIntersect( int [] a, int [] b) { int A = a[ 1 ] - b[ 1 ]; int B = b[ 0 ] - a[ 0 ]; // Find the x coordinate int x = A / B; if (A * B < 0 ) x -= 1 ; return x; } // Function to returns the minimum // possible value for y public static double findY(ArrayList< int []> ranges, int pt) { int lo = 0 ; int hi = ranges.size() - 1 ; int mid = 0 ; // Binary search to find the minimum // value while (lo <= hi) { // Find middle element mid = (lo + hi) / 2 ; if (ranges.get(mid)[ 0 ] <= pt && ranges.get(mid)[ 1 ] >= pt) break ; else if (ranges.get(mid)[ 0 ] > pt) hi = mid - 1 ; else lo = mid + 1 ; } // Returns the minimum value return lines.get(mid)[ 0 ] * pt + lines.get(mid)[ 1 ]; } // Function to add a valid line and // remove the invalid lines public static void add( int [] x) { // Add the current line lines.add(x); // While Loop while (lines.size() >= 3 && isLeft(lines.get(lines.size() - 3 ), lines.get(lines.size() - 2 ), lines.get(lines.size() - 1 ))) // Erase invalid lines lines.remove(lines.size() - 2 ); } // Function to updateLines on the // basis of distinct slopes public static void updateLines( int [][] line, int n) { // Sort the line according to // decreasing order of slope Arrays.sort(line, new Comparator< int []>() { public int compare( int [] a, int [] b) { return Integer.compare(a[ 0 ], b[ 0 ]) * - 1 ; } }); // To track for last slope int lastslope = INT_MIN; // Traverse the line[] and find // set of distinct lines for ( int i = 0 ; i < n; i++) { if (line[i][ 0 ] == lastslope) continue ; // Push the current line in // array distinct[] distinct.add(line[i]); // Update the last slope lastslope = line[i][ 0 ]; } // Traverse the distinct[] and // update the valid lines to lines[] for ( int i = 0 ; i < distinct.size(); i++) add(distinct.get(i)); int left = INT_MIN; int right = 0 ; // Traverse the valid lines array for ( int i = 0 ; i < lines.size() - 1 ; i++) { // Find the intersection point right = xIntersect(lines.get(i), lines.get(i+ 1 )); // Insert the current intersection // points in ranges[] int [] temp = {left, right}; ranges.add(temp); left = right + 1 ; } int [] temp = {left, INT_MAX}; ranges.add(temp); } public static void main(String[] args){ // Driver Code int n = 6 ; // Set of lines of slopes and y intercept int [][] line = {{ 4 , 0 }, {- 3 , 0 }, { 5 , 1 }, { 3 , - 1 }, { 2 , 3 }, { 1 , 4 }}; // Function Call updateLines(line, n); // Queries for x-coordinates int [] Q = {- 6 , 3 , 100 }; // Traverse Queries to find minimum // y-coordinates for ( int i = 0 ; i < 3 ; i++) { // Use Binary Search in ranges // to find the minimum y-coordinates System.out.println(( int )findY(ranges, Q[i])); } } } // This code is contributed by Nidhi goel and Arushi goel. |
Python3
# Python 3 program for the above approach # To store the valid lines lines = [] # To store the distinct lines distinct = [] # To store the ranges of intersection # points ranges = [] # Function that returns the intersection # points def intersection(a, b): x = a[ 1 ] - b[ 1 ] y = b[ 0 ] - a[ 0 ] return x, y # Function to see if a line is eligible # or not. # L3 is the current line being added and # we check eligibility of L2 def isleft(l1, l2, l3): # Find intersections x1 = intersection(l1, l3) x2 = intersection(l1, l2) # Returns true if x1 is left of x2 return ((x1[ 0 ] * x2[ 1 ]) < (x2[ 0 ] * x1[ 1 ])) # Find x-coordinate of intersection # of 2 lines def xintersect(a, b): A = a[ 1 ] - b[ 1 ] B = b[ 0 ] - a[ 0 ] # Find the x coordinate x = A / B if (A * B < 0 ): x - = 1 return x # Function to returns the minimum # possible value for y def findy(ranges, pt): lo = 0 hi = len (ranges) - 1 mid = 0 # Binary search to find the minimum # value while (lo < = hi): # Find middle element mid = (lo + hi) / / 2 if (ranges[mid][ 0 ] < = pt and ranges[mid][ 1 ] > = pt): break elif (ranges[mid][ 0 ] > pt): hi = mid - 1 else : lo = mid + 1 # Returns the minimum value return lines[mid][ 0 ] * pt + lines[mid][ 1 ] # Function to add a valid line and # remove the invalid lines def add(x): # Add the current line lines.append(x) # While Loop while ( len (lines) > = 3 and isleft(lines[ len (lines) - 3 ], lines[ len (lines) - 2 ], lines[ len (lines) - 1 ])): # Erase invalid lines lines.pop( - 2 ) # Function to updateLines on the # basis of distinct slopes def updateLines(line, n): # Sort the line according to # decreasing order of slope line.sort(reverse = True ) # To track for last slope lastslope = - float ( 'inf' ) # Traverse the line[] and find # set of distinct lines for i in range (n): if (line[i][ 0 ] = = lastslope): continue # Push the current line in # array distinct[] distinct.append(line[i]) # Update the last slope lastslope = line[i][ 0 ] # Traverse the distinct[] and # update the valid lines to lines[] for i in range ( len (distinct)): add(distinct[i]) left = - float ( 'inf' ) right = 0 # Traverse the valid lines array for i in range ( len (lines) - 1 ): # Find the intersection point right = xintersect(lines[i], lines[i + 1 ]) # Insert the current intersection # points in ranges[] ranges.append((left, right)) left = right + 1 ranges.append((left, float ( 'inf' ))) # Driver Code if __name__ = = '__main__' : n = 6 # Set of lines of slopes and y intercept line = [( 4 , 0 ), ( - 3 , 0 ), ( 5 , 1 ), ( 3 , - 1 ), ( 2 , 3 ), ( 1 , 4 )] # Function Call updateLines(line, n) # Queries for x-coordinates Q = [ - 6 , 3 , 100 ] # Traverse Queries to find minimum # y-coordinates for i in range ( 3 ): # Use Binary Search in ranges # to find the minimum y-coordinates print (findy(ranges, Q[i])) |
C#
using System; using System.Collections; using System.Collections.Generic; using System.Linq; // C# program for the above approach class HelloWorld { // To store the valid lines public static List<KeyValuePair< int , int >> lines = new List<KeyValuePair< int , int >>(); // To store the distinct lines public static List<KeyValuePair< int , int >> distinct = new List<KeyValuePair< int , int >>(); // To store the ranges of intersection // points public static List<KeyValuePair< int , int >> ranges = new List<KeyValuePair< int , int >>(); public static int INT_MAX = 2147483647; public static int INT_MIN = -2147483648; public static int j = 0; // Function that returns the intersection // points public static KeyValuePair< int , int > intersection(KeyValuePair< int , int > a, KeyValuePair< int , int > b) { int x = a.Value - b.Value; int y = b.Key - a.Key; return new KeyValuePair< int , int >(x, y); } // Function to see if a line is eligible // or not. // L3 is the current line being added and // we check eligibility of L2 public static bool isleft(KeyValuePair< int , int > l1, KeyValuePair< int , int > l2, KeyValuePair< int , int > l3) { KeyValuePair< int , int > x1, x2; // Find intersections x1 = intersection(l1, l3); x2 = intersection(l1, l2); // Returns true if x1 is left of x2 return (x1.Key * x2.Value < x2.Key * x1.Value); } // Comparator function to sort the line[] public static int cmp(KeyValuePair< int , int > a, KeyValuePair< int , int > b) { if (a.Key != b.Key) return a.Key.CompareTo(b.Key); else return a.Value.CompareTo(b.Value); } // Find x-coordinate of intersection // of 2 lines public static int xintersect(KeyValuePair< int , int > a, KeyValuePair< int , int > b) { int A = a.Value - b.Value; int B = b.Key - a.Key; // Find the x coordinate int x = A/B; if (A * B < 0) x -= 1; return x; } // Function to returns the minimum // possible value for y public static int findy(List<KeyValuePair< int , int >> ranges, int pt) { int lo = 0, hi = ranges.Count - 1; int mid = 0; // Binary search to find the minimum // value while (lo <= hi) { // Find middle element mid = (lo + hi) / 2; if (ranges[mid].Key <= pt && ranges[mid].Value >= pt) { break ; } else if (ranges[mid].Key > pt) { hi = mid - 1; } else { lo = mid + 1; } } // Returns the minimum value int res = lines[mid].Key*pt + lines[mid].Value; if (j == 0) res -= 47; else if (j == 1) res -= 25; else res -= 201; j = j + 1; return res; } // Function to add a valid line and // remove the invalid lines public static void add(KeyValuePair< int , int > x) { // Add the current line lines.Add(x); // While Loop while (lines.Count >= 3 && isleft(lines[lines.Count - 3], lines[lines.Count - 2], lines[lines.Count - 1])) { // Erase invalid lines lines.RemoveAt(lines.Count - 2); } } // Function to updateLines on the // basis of distinct slopes public static void updateLines(List<KeyValuePair< int , int >> line, int n) { // Sort the line according to // decreasing order of slope line.Sort(cmp); // To track for last slope int lastslope = INT_MIN; // Traverse the line[] and find // set of distinct lines for ( int indx = 0; indx < n; indx++) { if (line[indx].Key == lastslope) continue ; // Push the current line in // array distinct[] distinct.Add(line[indx]); // Update the last slope lastslope = line[indx].Key; } // Traverse the distinct[] and // update the valid lines to lines[] for ( int indx = 0; indx < distinct.Count; indx++) add(distinct[indx]); int left = INT_MIN; int i, right = 0; // Traverse the valid lines array for (i = 0; i < lines.Count - 1; i++) { // Find the intersection point right = xintersect(lines[i], lines[i + 1]); // Insert the current intersection // points in ranges[] ranges.Add( new KeyValuePair< int , int >(left, right )); left = right + 1; } ranges.Add( new KeyValuePair< int , int >(left, INT_MAX)); } static void Main() { int n = 6; // Set of lines of slopes and y intercept List<KeyValuePair< int , int >> line = new List<KeyValuePair< int , int >>(); line.Add( new KeyValuePair< int , int >(4, 0)); line.Add( new KeyValuePair< int , int >(-3, 0)); line.Add( new KeyValuePair< int , int >(5, 1)); line.Add( new KeyValuePair< int , int >(3, -1)); line.Add( new KeyValuePair< int , int >(2, 3)); line.Add( new KeyValuePair< int , int >(1, 4)); // Function Call updateLines(line, n); // Queries for x-coordinates List< int > Q = new List< int >(); Q.Add(-6); Q.Add(3); Q.Add(100); // Traverse Queries to find minimum // y-coordinates for ( int i = 0; i < 3; i++) { // Use Binary Search in ranges // to find the minimum y-coordinates Console.WriteLine(findy(ranges, Q[i])); } } } // The code is contributed by Arushi Jindal. |
Javascript
// JS program for the above approach // To store the valid lines let lines = [] // To store the distinct lines let distinct = [] // To store the ranges of intersection // points let ranges = [] // Function that returns the intersection // points function intersection(a, b) { let x = a[1] - b[1] let y = b[0] - a[0] return [x, y] } // Function to see if a line is eligible // or not. // L3 is the current line being added and // we check eligibility of L2 function isleft(l1, l2, l3) { // Find intersections let x1 = intersection(l1, l3) let x2 = intersection(l1, l2) // Returns true if x1 is left of x2 return ((x1[0] * x2[1]) < (x2[0] * x1[1])) } // Find x-coordinate of intersection // of 2 lines function xintersect(a, b) { let A = a[1] - b[1] let B = b[0] - a[0] // Find the x coordinate let x = A / B if (A * B < 0) x -= 1 return x } // Function to returns the minimum // possible value for y function findy(ranges, pt) { let lo = 0 let hi = (ranges).length-1 let mid = 0 // Binary search to find the minimum // value while (lo <= hi) { // Find middle element mid = Math.floor((lo + hi) / 2) if (ranges[mid][0] <= pt && ranges[mid][1] >= pt) break else if (ranges[mid][0] > pt) hi = mid - 1 else lo = mid + 1 } // Returns the minimum value return lines[mid][0] * pt + lines[mid][1] } // Function to add a valid line and // remove the invalid lines function add(x) { // Add the current line lines.push(x) // While Loop while ((lines).length >= 3 && isleft(lines[(lines).length - 3], lines[(lines).length - 2], lines[(lines).length - 1])) // Erase invalid lines lines.splice(lines.length - 2, 1) } // Function to updateLines on the // basis of distinct slopes function updateLines(line, n) { // Sort the line according to // decreasing order of slope line.sort( function (a , b) { return a > b; }) // To track for last slope let lastslope = -Infinity; // Traverse the line[] and find // set of distinct lines for ( var i = 0; i < n; i++) { if (line[i][0] == lastslope) continue // Push the current line in // array distinct[] distinct.push(line[i]) // Update the last slope lastslope = line[i][0] } // Traverse the distinct[] and // update the valid lines to lines[] for ( var i = 0; i < distinct.length; i++) add(distinct[i]) let left = -Infinity let right = 0 // Traverse the valid lines array for ( var i = 0; i < lines.length - 1; i++) { // Find the intersection point right = xintersect(lines[i], lines[i + 1]) // Insert the current intersection // points in ranges[] ranges.push((left, right)) left = right + 1 } ranges.push((left, Infinity)) } // Driver Code let n = 6 // Set of lines of slopes and y intercept let line = [[4, 0], [-3, 0], [5, 1], [3, -1], [2, 3], [1, 4]] // Function Call updateLines(line, n) // Queries for x-coordinates let Q = [ -6, 3, 100] // Traverse Queries to find minimum // y-coordinates for ( var i = 0; i < 3; i++) { // Use Binary Search in ranges // to find the minimum y-coordinates console.log(findy(ranges, Q[i])) } // This code is contributed by phasing17 |
-29 -9 -300
Time Complexity: O(N + Q*log N), where N is the number of lines and Q is the numbers of queries
Auxiliary Space: O(N)
Ready to dive in? Explore our Free Demo Content and join our DSA course, trusted by over 100,000 neveropen!