Basic search techniques

Introduction to Programming
Competitions
Search techniques
Dr Vincent Gramoli | Lecturer
School of Information Technologies
Roadmap
›  Complete search
›  Divide and conquer
›  Greedy
›  Coin change problems
›  Interval covering problems
2
Complete search
3
Complete search
Complete search
›  Also known as “brute force”, complete search is a method for solving a
problem by traversing the entire (or part of the) search space to obtain the
required solution.
›  In programming contests
-  complete search is used when the input size permits (analysis should help)
-  complete search problems are among the simplest and completed first
-  complete search submissions return Accepted or TLE as it is easy to code
-  complete search can be used for debugging your solution on small test cases
4
Example
Given an array A containing n ≤ 10K small integers ≤ 100K
(e.g., A = {10, 7, 3, 5, 8, 2, 9}, n = 7)
›  Find the largest and smallest elements of A (e.g., 10 and 2 for the above ex.)
5
Example
Given an array A containing n ≤ 10K small integers ≤ 100K
(e.g., A = {10, 7, 3, 5, 8, 2, 9}, n = 7)
›  Find the largest and smallest elements of A (e.g., 10 and 2 for the above ex.)
›  Complete search:
-  Try each element of A and check if it is the current largest (or smallest) element
seen so far.
-  Time is O(n).
6
Iterative complete search
Uva #441 – Lotto
›  Given 6 < k <13 sorted integers, enumerate in sorted order all possible
subsets of size 6 of these integers in sorted order.
7
Iterative complete search
Uva #441 – Lotto (con’t)
›  Given 6 < k <13 sorted integers, enumerate in sorted order all possible
subsets of size 6 of these integers in sorted order.
›  Since the size of the required subset is always 6, the easiest solution is to
use six nested loops as shown below. (only 12C6 output lines)
for (int i = 0; i < k; i++) scanf(“%d“, &S[i]); // sorted k int
for (int a = 0; a<k-5; a++) // 6 nested loops
for (int b = a+1; b<k-4; b++)
for (int c = b+1; c<k-3; c++)
for (int c = b+1; c<k-3; c++)
for (int c = b+1; c<k-3; c++)
for (int c = b+1; c<k-3; c++)
printf(“%d %d %d %d %d %d\n“,S[a],S[b],S[c],S[d],S[e],S[f])
8
Recursive complete search
Uva #750 – Eight queens problem
›  In chess (with an 8x8 board), it is possible to place 8 queens on the board
such that no two queens attack each other. Output, in lexicographical order,
all such possible arrangements given the position of one queen.
Figure from http://www.aiai.ed.ac.uk/~gwickler/eightqueens.html
9
Recursive complete search
Uva #750 – Eight queens problem (con’t)
›  Naïve approach:
-  Enumerate all combinations of 8 different cells out of the 8x8 = 64 possible cells in
a chess board and see if the 8 queens can be placed at this positions.
-  There are 64C8 ⋍ 4B such possibilities (=> Time Limit Exceeded)
›  Other approach:
-  There cannot be two queens on same column, so place one queen per column
-  There are only 88 ⋍ 17M possibilities now (still TLE)
10
Recursive complete search
Uva #750 – Eight queens problem (con’t)
Solution
›  No two queens can share the same column or the same row so the problem
becomes valid permutations of 8! ⋍ 40K row positions.
›  The value of row[i] describes the row position of the queen in column i
(e.g., row = {0,2,4,8,1,3,5,7} is one solution)
›  No two queens A and B can share any diagonal ⇔ |xA - xB| ≠ |yA - yB|
›  AC (accepted)
11
Recursive complete search
Uva #750 – Eight queens problem (con’t)
import java.util.*;
class Main { /* 8 Queens Chess Problem */
private static int[] row = new int[9];
private static int TC, a, b, lineCounter; // global variables
private static boolean place(int col, int tryrow) {
for (int prev = 1; prev < col; prev++) // check placed queens
if (row[prev] == tryrow || (Math.abs(row[prev] - tryrow) == Math.abs(prev - col)))
return false; // infeasible solution
return true;
}
›  Pruning: give up if previous queen share same diagonal
12
Recursive complete search
Uva #750 – Eight queens problem (con’t)
private static void backtrack(int col) {
for (int tryrow = 1; tryrow <= 8; tryrow++) { // try all rows
if (place(col, tryrow)) { // if a queen can go here
row[col] = tryrow; // put this queen in this col and row
if (col == 8 && row[b] == a) { // q at (a,b)
System.out.printf("%2d
%d", ++lineCounter, row[1]);
for (int j = 2; j <= 8; j++) System.out.printf(" %d“,
row[j]);
System.out.printf("\n");
} else backtrack(col + 1); // recursively try next column
} } }
›  Recursively try each column in increasing order with each of the 1 to 8 rows
›  Reduce the initial complexity of 8! by pruning
13
Recursive complete search
Uva #750 – Eight queens problem (con’t)
}
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
TC = sc.nextInt();
while (TC-- > 0) {
a = sc.nextInt();
b = sc.nextInt();
for (int i = 0; i < 9; i++) row[i] = 0;
lineCounter = 0;
System.out.printf("SOLN
COLUMN\n");
System.out.printf(" #
1 2 3 4 5 6 7 8\n\n");
backtrack(1); // generate all 8! candidate solutions
if (TC > 0) System.out.printf("\n");
}
}
14
Complete search tips
Tips
›  Sometimes it might be hard to know whether the time limit will be reached
›  Filter programs examine a lot of candidate solutions before discarding them
(e.g., 88 naïve approach to 8-queens problem). They are generally iterative.
›  Generator prune the space of candidate solutions ahead of time. They are
generally recursive.
›  Use symmetry if necessary: in the 8-queens problem there are 92 solutions
but they can be generated by rotation/reflection of 12 unique solutions. This
may complicate the code though.
›  Pre-computation: since they are only 92 solutions, one could write a script
that generates an array[92][8] declaration so that the program simply pick
the element satisfying the problem constraints.
›  Leverage the cache: use the expected O(n log n) C++ quicksort in C++ STL
algorithm::sort rather than the true O(n log n) heapsort: its root-to-leaf, leafto-root operations span a large range of indices (producing cache misses)
15
Divide and Conquer
16
Divide and conquer
Divide and conquer (D&C)
›  A problem solving paradigm in which the problem is made simpler by
dividing it into smaller parts and then conquering each part. The steps:
1.  Divide the original problem into sub-problems (usually by half or nearly)
2.  Find (sub)-solutions for each of these sub-problems (which are easier)
3.  If needed, combine the sub-solutions to get the complete solution.
›  D&C is used by Quick sort, Merge sort, Heap sort and Binary search
›  D&C is used by structures: BST, Heap, Segment tree, Fenwick tree
17
Example
Given an array A containing n ≤ 10K small integers ≤ 100K
(e.g., A = {10, 7, 3, 5, 8, 2, 9}, n = 7)
›  Find the kth smallest element in A (e.g., if k=2, then return 3 in the ex.)
18
Example
Given an array A containing n ≤ 10K small integers ≤ 100K
(e.g., A = {10, 7, 3, 5, 8, 2, 9}, n = 7)
›  Find the kth smallest element in A (e.g., if k=2, then return 3 in the ex.)
1.  We could use complete search
-  Find the smallest and delete it. Find the smallest value in the new array (2nd
smallest value) and so on k times to find the kth value.
-  The expected value of k is n/2, so expected time is O(n/2 * n) = O(n2)
19
Example
2.  But we should use divide-and-conquer
-  Sort the array A in O(n log n) (cf. lecture 2 on data structures)
-  Return A[k-1]
-  Time is O(n log n)
20
Example
3.  A more efficient divide-and-conquer solution
›  Key idea: use O(n) RandomizedPartition(A, l, r) from quick sort that
partitions a given range [l..r] of the array A around a (random) pivot.
-  Pivot A[p] is one of the element of A where p∈ [l..r]
-  After partitioning, all elements ≤ A[p] are placed before the pivot and all elements >
A[p] are placed after the pivot
-  The resulting index q of the pivot indicates that it is the qth element
›  We want to find k
-  If q+1 = k, A[q] is the desired answer. We return this value and stop.
-  If q+1 > k, the desired answer is in the left partition, e.g., A[0..q-1]
-  If q+1 < k, the desired answer is in the right partition, e.g., A[q+1..n-1]
›  Retry with the partition that contains k until you pick k as the pivot
21
Divide and conquer
3.  A more efficient divide-and-conquer solution (con’t)
int RandomizedSelet(int A[], int l, int r, int k) {
if (l == r) return A[l];
int q = RandomizedPartition(A, l, r);
if (q+1 == k) return A[q];
else if (q+1 > k) return RandomizedSelect(A, l, q-1, r, k);
else RandomizedSelect(A, l, q+1, r, k);
}
›  Expected time is O(n)
-  Assume n is a power of 2
-  n steps for round 0, n/(2i) in expectation for the ith round…
-  Total: n + n/2 + n/4 + … + 1 < 2n leading to O(n)
22
Binary search
Binary search can be considered a D&C technique although it decreases by
half without combining the results.
›  The ordinary usage of binary search is to search a static sorted array
-  Check the middle of the sorted array and stop if no more items or result found
-  Otherwise, continue searching in the left or right sub-array where the result is
-  C++ STL algorithm::lower_bound / Collections.binarySearch
›  Binary seach can be used in other cases
23
Binary search
My Ancestor problem [Thailand ICPC National Contest 2009]
›  Given a weighted (family) tree of up to N ≤ 80K vertices with a special trait:
vertex value are increasing from root to leaves. From a starting vertex v, find
the ancestor vertex closest to the root hat has weight at least P.
›  There are up to Q ≤ 20K such offline queries. Naive O(N) scan leads to
O(QN) ~ 1.6M
A
B
C
8
7
5
3
8
36
20
9
10
Example: if P=4, answer
is B with value 5.
(If P ≥ 9, no solution.)
20
v
24
Binary search
My Ancestor problem (con’t)
›  A solution is to store all 20K queries. Traverse the tree just once starting
from the root using a O(N) preorder tree traversal algorithm that remembers
the partial root-to-current-vertex sequence as it executes.
A
B
C
8
7
5
3
8
3
3
3
3
5
5
8
36
36
10
7
7
20
10
20
8
9
36
20
9
3
20
v
›  We can perform a O(log N) binary search to find the ancestor and O(Q)
iterations to output the results => O(Qlog N) time.
25
Bisection method
26
Bisection method
Bisection method example
›  You buy a car with loan and now want to pay the loan in monthly installments
of d dollars for m months.
›  Suppose the value of the car is originally v dollars and the bank charges an
interest rate of i% for an unpaid loan at the end of each month.
›  What is the amount of money d that you must pay per month (to 2 digits after
the decimal point)?
27
Bisection method
Bisection method example
›  You buy a car with loan and now want to pay the loan in monthly installments
of d dollars for m months.
›  Suppose the value of the car is originally v dollars and the bank charges an
interest rate of i% for an unpaid loan at the end of each month.
›  What is the amount of money d that you must pay per month (to 2 digits after
the decimal point)?
Example:
›  Suppose d=576.19, m=2, v=1000, and i=10%
›  After one month, your debt becomes 1000 x 1.1 – 576.19 = 523.81
›  After two months, you debt becomes 523.81 x 1.1 – 576.19 ⋍ 0
28
Bisection method
Bisection method example
›  You buy a car with loan and now want to pay the loan in monthly installments
of d dollars for m months.
›  Suppose the value of the car is originally v dollars and the bank charges an
interest rate of i% for an unpaid loan at the end of each month.
›  What is the amount of money d that you must pay per month (to 2 digits after
the decimal point)?
Example:
›  Suppose d=576.19, m=2, v=1000, and i=10%
›  After one month, your debt becomes 1000 x 1.1 – 576.19 = 523.81
›  After two months, you debt becomes 523.81 x 1.1 – 576.19 ⋍ 0
Find the root d such that the debt payment is function f(d,m,v,i) = 0.
29
Bisection method
Bisection method example (con’t)
›  An easy way to solve this root finding problem is to use the bisection
method:
-  We pick a reasonable range as a starting point
-  We want to fix d within the range [a..b] where
-  a=0.01, as we have to pay at least 1 cent
-  b=(1+i%) × v as the earliest we can complete the payment is m=1 if we pay b
a
b
d=(a+b)/2
f(d,m,v,i)
action
0.01
1100
550.005
< by 54.9895
increase
825.0025
> by 522.50525
decrease
550.005 1100
550.005 825.0025 687.50375 > by 233.757875
decrease
…
…
…
…
…
…
576.19046 ⋍ (within ℇ)
…
return 576.19
30
Bisection method
Bisection method example (con’t)
›  Requires O(log2 ((b-a)/ℇ)) iterations to get an answer that is good enough
(whose error is smaller than the threshold ℇ)
›  If we use 1e-9
#define EPS 1e-9
-  The answer is found in ⋍ 40 iterations
›  If we use 1e-15.
#define EPS 1e-15
-  The answer is found in ⋍ 60 iterations
›  The bisection method is much faster than a complete search
31
Greedy
32
Greedy
Greedy
›  An algorithm is greedy if it makes the locally optimal choice at each step with
the hope of eventually reaching the globally optimal solution.
›  For a greedy algorithm to work, the problem
-  must have sub-problems
-  each sub-solution appears optimal at the time we find them (no need to reconsider)
›  In programming contests
-  rarely produces Time Limit Exceeded as it is usually lightweight
-  may produce Wrong Answer
-  rather use complete search / dynamic prog. as they are usually correct (if input size
is low enough).
-  Beware that sometimes problem authors choose ambiguous limits.
33
Example
Given and array A containing n ≤ 10K small integers ≤ 100K
(e.g., A = {10, 7, 3, 5, 8, 2, 9}, n = 7)
›  Find the largest gap g such that x,y∈A and g = |x-y| (e.g., 8 in the ex.)
1.  We can simply use complete search by considering all possible two
integers x and y in A, checking if the gap between them is the largest for
each pair, leading to time O(n2).
34
Example
2.  The greedy solution
›  Find the smallest element x and largest element y of A
-  Use the O(n) divide and conquer to find the 1st and nth smallest element
›  Compute g = y-x
›  Expected time is O(n)
35
Coin Change Problems
36
Coin change problems
›  Given a target amount V cents and a list of denominations of n coins, what is
the minimum number of coins that we must use to represent V?
›  Example: V=42 cents, n=4 and coinValue = {25, 10, 5, 1} cents
-  Solution?
37
Coin change problems
›  Given a target amount V cents and a list of denominations of n coins, what is
the minimum of number of coins that we must use to represent V?
›  Example: V=42 cents, n=4 and coinValue = {25, 10, 5, 1} cents
-  Solution: 5 (25+10+5+1+1)
38
Coin change problems
›  Given a target amount V cents and a list of denominations of n coins, what is
the minimum of number of coins that we must use to represent V?
›  Example: V=42 cents, n=4 and coinValue = {25, 10, 5, 1} cents
-  Solution: 5 (25+10+5+1+1)
›  Greedy solution: until you reach V, select the largest count denomination that
is not larger than the remaining amount.
-  Problem has sub-problems: 35 is 25+10 or 7 is 5+1+1, etc.
-  For this set of coins, all sub-solutions lead to the optimal solution
39
Coin change problems
›  This greedy algorithm does not work with all sets of coins denominations
›  Take V=7, n=4 and coinValue = {1, 3, 4, 5} cents
-  a greedy algorithm would choose 3 coins, 5+1+1
-  The optimal solution is 2 coins, 4+3
›  The right solution Is dynamic programming (DP will be in a coming lecture)
40
Interval Covering Problems
41
Interval covering problems
Uva #10382 – Watering Grass
›  Given n≤104 sprinklers centered vertically in a horizontal strip of grass L
meters long and W meters wide. For each sprinkler, its distance from the left
end of the center line and its radius of operation are given. What is the
minimum number of sprinklers that should be turned on to water the entire
strip?
B
A
C
D
E
F
H
G
Example: n=8
The solution is 6 (A, B,
D, E, F, H are on and
C, G are off).
Figure from http://uva.onlinejudge.org/
42
Interval covering problems
Uva #10382 – Watering Grass (con’t)
›  We cannot solve this problem with a complete search (or brute force)
strategy that tries all possible subsets of sprinklers to be turned on since the
number of subset of sprinklers goes up to 21000.
›  This problem is an interval covering problem with a geometric twist as
sprinklers have circles of action instead of intervals of action.
43
Interval covering problems
Uva #10382 – Watering Grass (con’t)
›  We cannot solve this problem with a complete search (or brute force)
strategy that tries all possible subsets of sprinklers to be turned on since the
number of subset of sprinklers goes up to 21000.
›  This problem is an interval covering problem with a geometric twist as
sprinklers have circles of action instead of intervals of action.
1.  Convert circles into intervals: If a circle is centered in (x,y) then its interval
of action is [x-dx, x+dx] where dx = sqrt(R2 – (W/2)2) (cf. right triangle
below).
W/2
R
dx
44
Interval covering problems
Uva #10382 – Watering Grass (con’t)
2.  Greedy algorithm:
a. 
Sort intervals by increasing left endpoint and by decreasing right endpoint (to
break ties) {A, B, C, D, E, F, G, H}
b. 
Take the interval that covers as far right as possible with uninterrupted coverage
45
Interval covering problems
Uva #10382 – Watering Grass (con’t)
2.  Greedy algorithm:
a. 
Sort intervals by increasing left endpoint and by decreasing right endpoint (to
break ties) {A, B, C, D, E, F, G, H}
b. 
Take the interval that covers as far right as possible with uninterrupted coverage
A (it has to)
B (connected to A)
Ignore C (inside B)
D (B&E not connected)
B
A
C
D
E
F
H
G
E, F
Ignores G (not as far
right as possible)
H
46