Project Euler problem 67: find maximum cost path in 100-row triangle - data-structures

In Project Euler's problem 67 there is a triangle given and it contains 100 rows. For e.g.
5
9 6
4 6 8
0 7 1 5
I.e. 5 + 9 + 6 + 7 = 27.
Now I have to find the maximum total from top to bottom in given 100 rows triangle.
I was thinking about which data structure should I use so that problem will get solved efficiently.

You want to store this as a directed acyclic graph. The nodes are the entries of your triangular array, and there is an arrow from i to j iff j is one row lower and immediately left or right of i.
Now you want to find the maximum weight directed path (sum of the vertex weights) in this graph. In general, this problem is NP-complete, however, as templatetypedef points out, there are efficient algorithms for DAGs; here's one:
algorithm dag-longest-path is
input:
Directed acyclic graph G
output:
Length of the longest path
length_to = array with |V(G)| elements of type int with default value 0
for each vertex v in topOrder(G) do
for each edge (v, w) in E(G) do
if length_to[w] <= length_to[v] + weight(G,(v,w)) then
length_to[w] = length_to[v] + weight(G, (v,w))
return max(length_to[v] for v in V(G))
To make this work, you will need to weight the length of the path to be the size of the target node (since all paths include the source, this is fine).

Which language are you using?
A muti-dimensional array is probably the best way to store the values, then depending on the language, there are options for how you store pointers or references to where in the arrays you are.

To build off of #Matt Wilson's answer, using a two-dimensional array to hold the numbers would be a fairly simple solution. In your case, you would encode the triangle
5
9 6
4 6 8
0 7 1 5
as the array
[5][ ][ ][ ]
[9][6][ ][ ]
[4][6][8][ ]
[0][7][1][5]
From here, a node at position (i, j) has children at (i + 1, j) and (i + 1, j + 1) and parents at position (i - 1, j) and (i - 1, j - 1), assuming those indices are valid.
The advantage of this approach is that if your triangle has height N, the space required for this approach is N2, which is just less than twice the N(N + 1) / 2 space required to actually store the elements. A linked structure like an explicit graph would certainly use more memory than that.

I believe this is a Project Euler problem.
It can't be represented with a binary tree. Any kind of graph is overkill as well, though the problem can be solved with a longest path algorithm in directed acyclic graphs. Edit: Nevermind that, it is only useful if the edges are weighted, not the nodes.
A two dimensional vector (e.g. vector<vector<int> >) is more than enough to represent such triangles, and there is a straightforward solution using such representation.

#include <iostream>
#include <fstream>
#include <vector>
#include <sstream>
int main() {
std::vector<std::vector<int> > lines;
lines.resize(100);
std::ifstream input("triangle.txt");
for (int i = 0; i < 100; i++) {
for (int j = 0; j < i + 1; j++) {
std::string number_string;
input >> number_string;
std::istringstream temp(number_string);
int value = 0;
temp >> value;
lines[i].push_back(value);
}
}
std::vector<int> path1;
path1.resize(100);
std::vector<int> path2;
path2.resize(100);
for (int i = 0;i < 100;i++)
path1[i] = lines[99][i];
for (int i = 98; i >= 0;i--) {
for(int j = 0;j < i+1;j++) {
if(path1[j] > path1[j + 1]){
path2[j] = path1[j] + lines[i][j];
} else{
path2[j] = path1[j + 1] + lines[i][j];
}
}
for (int i = 0;i < 100;i++)
path1[i] = path2[i];
}
std::cout << path1[0] << std::endl;
}

Related

How can we find the largest contiguous region of a graph with two different "ID"s?

I've recently learned about the Flood-Fill Algorithm, an algorithm that can take a graph and assign each node a component number in O(N) time.
For example, a common problem that can be solved efficiently with the Flood-Fill Algorithm would be to find the largest region in a N*N board, where every node in the region is adjacent to another node with the same ID either directly up, down, to the left, or to the right.
In this board, the largest regions would both be of size 3, made up of all 1s and all 9s respectively.
However, I recently started wondering if we could extend this problem; specifically, if we could find the largest region in a graph such that every node in the region is adjacent to another node with two possible IDs. In the above board, the largest such region is made up of 1s and 9s, and has a size of 7.
Here was my thought process in trying to solve this problem:
Thought 1: O(N^4) Algorithm
We can solve this in O(N^4) time using a basic flood-fill algorithm. We do this by testing all O(N^2) pairs of horizontally or vertically adjacent squares. For every pair of squares, if they have different IDs, then we run a flood-fill from one of the two squares.
Then, by modifying the flood-fill algorithm so that it travels to squares with one of the two possible IDs, we can test each pair in O(N^2) time --> O(N^2) pairs * O(N^2) flood fill per pair = O(N^4) algorithm.
Then, I had an insight: An Possibly O(N^2) Algorithm
First, we run a regular flood-fill through the board and separate the board into a "component graph" (where each component in the original graph is reduced to a single node).
Now, we do a flood-fill through the edges of the component graph instead of the nodes. We mark each edge with a pair of integers signifying the two IDs inside the two components which it connects, before flood-filling through the edges as if they themselves were nodes.
I believe that this, if implemented correctly, would result in a O(N^2) algorithm, because an upper bound for the number of edges in a N*N board is 4*N*N.
Now, my question is, is my thought process logically sound? If not, can somebody suggest another algorithm to solve this problem?
Here is the algorithm that I wrote to solve your problem. It expands on your idea to flood-fill through the edges (great idea, by the way) and is able to output the correct answer for a 250*250 grid in less than 300ms, with less than 30 megabytes of memory allocated.
Here is the problem that I managed to find online that matches your question exactly, and it is also where I tested the validity of my algorithm:
USACO Problem
Note that the USACO Problem requires us to find the largest single-id component before finding the largest double-id component. In my algorithm, the first step is actually necessary in order to reduce the whole board into a component graph.
Here's my commented C++ Code:
#include <iostream>
#include <fstream>
#include <cmath>
#include <algorithm>
#include <vector>
#include <unordered_set>
using namespace std;
// board to hold square ids and comp[][] to mark component numbers
vector <vector<int>> board, comp;
vector <int> comp_size = {-1}; // size of those components
vector <int> comp_id = {-1}; // id contained within those components
vector <unordered_set <int>> adj = {{}}; // component graph adjacency list
vector <bool> visited; // component graph visited array
void dfs(int x, int y, int N, int id, int curr_comp){
if(x < 0 || x >= N || y < 0 || y >= N){return;}
else if(board[x][y] != id){
if(comp[x][y] == 0){return;}
// construct component graph adjacency list during the first flood-fill
adj[comp[x][y]].insert(curr_comp);
adj[curr_comp].insert(comp[x][y]);
// this is why we use an unordered_set: it automatically eliminates
// collisions
return;
}
else if(comp[x][y]){return;}
++comp_size[curr_comp];
comp[x][y] = curr_comp;
dfs(x-1, y, N, id, curr_comp);
dfs(x+1, y, N, id, curr_comp);
dfs(x, y-1, N, id, curr_comp);
dfs(x, y+1, N, id, curr_comp);
}
void dfs2(int curr, int id1, int id2, int &size){
visited[curr] = true;
// recurse from all valid and adjacent components to curr
vector <int> to_erase;
for(int item : adj[curr]){
if(visited[item]){continue;}
if(comp_id[item] == id1 || comp_id[item] == id2){
to_erase.push_back(item);
size += comp_size[item];
dfs2(item, id1, id2, size);
}
}
// we erase all edges connecting the current component AT THE SAME TIME to
// prevent std::unordered_set iterators from being invalidated, which would
// happen if we erased items as we iterated through adj[curr]
for(int item : to_erase){
adj[curr].erase(item);
adj[item].erase(curr);
}
return;
}
int main()
{
ifstream fin("multimoo.in");
ofstream fout("multimoo.out");
int N;
fin >> N;
board = vector <vector<int>> (N, vector <int> (N));
for(int i = 0; i < N; ++i){
for(int j = 0; j < N; ++j){
fin >> board[i][j];
}
}
// Input Done
comp = vector <vector<int>> (N, vector <int> (N, 0)); // note that comp[i][j] = 0 means not visited yet
// regular flood-fill through all the nodes
for(int i = 0, curr_comp = 1; i < N; ++i){
for(int j = 0; j < N; ++j){
if(comp[i][j]){continue;}
// add information about the current component
comp_size.push_back(0);
comp_id.push_back(board[i][j]);
adj.push_back({});
dfs(i, j, N, board[i][j], curr_comp++);
}
}
fout << *max_element(comp_size.begin(), comp_size.end()) << endl;
int ANS = 0;
for(unsigned int i = 1; i < comp_size.size(); ++i){
// no range-for loop here as we erase elements while iterating, which
// may invalidate unordered_set iterators; instead, we use a while-loop
while(!adj[i].empty()){
int size = comp_size[i], curr = *(adj[i].begin());
visited = vector <bool> (comp_size.size(), false); // reset visited
dfs2(i, comp_id[i], comp_id[curr], size);
ANS = max(ANS, size);
}
}
fout << ANS << endl;
return 0;
}
As for the time complexity, I personally am not very sure. If somebody could help analyze this algorithm to determine its complexity, I'd greatly appreciate it!
Your algorithm works...
As far as I can tell, flood filling over your induced graph indeed gives all possible components, after which it's simple to find the largest one.
...but I'm not sure about the runtime
You correctly say that there are O(N^2) edges in the original graph, and therefore O(N^2) nodes in the induced graph. However, these nodes are no longer guaranteed to be in a nice grid, which may leave more than O(N^2) induced edges.
For example, consider the large "1-block" in your example. This block has 6 edges, which will give a complete graph with 6 vertices, as all these edges-turned-vertices are connected. This may give you an induced graph with more than O(N^2) edges, making it impossible to find components in O(N^2) time.
Therefore, I believe that the algorithm will not run in O(N^2), but I'm unsure of the actual runtime, as it will depend on what exactly the algorithm does at this point. The question only notes flood fill, but I think it had not imagined this situation.
Consider the following 9x9 grid:
232323232
311111113
212313212
313212313
212313212
313212313
212313212
313212313
212313212
The idea is simple: it's a single large component designed to border as many small components as possible. The induced graph here would be a single almost-complete graph with O(N^2) vertices and O(N^4) edges. Alternatively, if we only link the (1,2) edges with other (1,2) edges, and similar for (1,3) edges and other (1,3) edges, we will have a slightly less-connected graph, but it would still consist of two components with each O(N^4) edges, albeit with a lower constant.
Therefore, creating this graph would take at least O(N^4) time, as would traversing it. This is the time I would argue that the algorithm takes, but I cannot prove that there are no possible optimizations that improve upon this.
We could achieve the optimal O(N^2) complexity by smartly doing our DFS from each pivot component.
Explanation:
First create the set of same-valued components and relationship-map to their neighbours
Notice that for the flood-zone we are looking for atmost 2 distinct values.
Lets say we consider the point (i, j) and look at its neighbours
For each 2-value-pair, say [v_ij, v_neighbour] => do a BFS from this (i,j) pivot point while only collecting nodes such that node-value is one of [v_ij, v_neighbour]
Notice that each component is visited only constant times for BFS (we ensure that by deleting the reverse-edge from child->parent while doing BFS).
Because of (5), our complexity remains O(N^2)
Working code in Python:
from queue import Queue
class Comp:
def __init__(self, point, value):
self.members = {point}
self.value = value
self.neighbours = set()
self.pivot = point
def can_add_member(self, value):
return value == self.value
def add_member(self, point):
self.members.add(point)
def add_neighbour(self, neighbour_comp):
self.neighbours.add(neighbour_comp)
def __str__(self):
return f'[M:%d, V:%d, N:%d]' % (len(self.members), self.value, len(self.neighbours))
def find_largest_flood_region(D):
point_to_comp_map = {}
N, M = len(D), len(D[0])
# Step-1: Create same-value connected-components:
for x in range(N):
for y in range(M):
if (x, y) in point_to_comp_map:
continue
comp_xy = Comp((x, y), D[x][y])
point_to_comp_map[(x, y)] = comp_xy
pq = Queue()
pq.put((x, y))
while pq.qsize() > 0:
i, j = pq.get()
for l, m in [(i-1, j), (i+1, j), (i, j-1), (i, j+1)]:
if 0 <= l < N and 0 <= m < M and (l, m) not in point_to_comp_map and D[l][m] == D[x][y]:
comp_xy.add_member((l, m))
point_to_comp_map[(l, m)] = comp_xy
pq.put((l, m))
# Step-2: Create the relationship-map between the components created above
for x in range(N):
for y in range(M):
comp_xy: Comp = point_to_comp_map[(x, y)]
for i, j in [(x-1, y), (x+1, y), (x, y-1), (x, y+1)]:
if 0 <= i < N and 0 <= j < M and D[i][j] != D[x][y]:
comp_ij: Comp = point_to_comp_map[(i, j)]
comp_xy.add_neighbour(comp_ij)
comp_ij.add_neighbour(comp_xy)
# Do BFS one by one on each unique component:
unique_comps = set(point_to_comp_map.values())
max_region = 0
for comp in unique_comps:
potential_values = set([neigh_comp.value for neigh_comp in comp.neighbours])
for value in potential_values:
value_set = {value, comp.value}
region_value = 0
pq = Queue()
pq.put(comp)
while pq.qsize() > 0:
comp_xy: Comp = pq.get()
region_value += len(comp_xy.members)
for ncomp in comp_xy.neighbours:
if ncomp.value in value_set:
if comp_xy in ncomp.neighbours:
ncomp.neighbours.remove(comp_xy)
pq.put(ncomp)
max_region = max(max_region, region_value)
return max_region
D = [
[9,2,7,9],
[1,1,9,9],
[3,1,4,5],
[3,5,6,6]
]
print(find_largest_flood_region(D))
Output:
7
We can show that solving this in O(n), where n is the number of elements in the matrix, is possible with two passes of a flood-fill union-find routine without a depth-first search.
Given
9 2 7 9
1 1 9 9
3 1 4 5
3 5 6 6
after we label with flood fill, we have:
A B C D
E E D D
F E G H
F I J J
Now that we know each component's size, we can restrict each cell to testing its best connection to a different component left or up. We only need to check one field in a map on the component, the one pointing to the same number, potentially create a new component of reference, or merge two.
In the following example, we'll label components with more than one value with two letters unrelated to their original components. Each cell visited can generate at most two new components and updates at most two components so the complexity remains O(n).
Iterating left to right, top to bottom:
A0: {⊥: 1, 2: AA, 1: DD}
B0: {⊥: 1, 9: AA, 7: BB, 1: EE}
AA = {size: 2}
C0: {⊥: 1, 2: BB, 9: CC}
BB = {size: 2}
D0: {⊥: 3, 7: CC}
CC = {size: 4}
E0: {⊥: 3, 9: DD}
DD = {size: 4}
E1: {⊥: 3, 9: DD, 2: EE}
EE = {size: 4}
D1: {⊥: 3, 7: CC, 1: DD}
DD updates to size 7
D2: {⊥: 3, 7: CC, 1: DD}
F0: {⊥: 2, 1: FF}
FF = {size: 5}
... etc.

Placing blocks and calculating height of the highest tower

Series of k blocks is given (k1, k2, ..., ki). Each block starts on position ai and ends on position bi and its height is 1. Blocks are placed consecutively. If the block overlaps another one, it is being attached on its top. My task is to calculate the highest tower of blocks.
I have created an algorithm which time complexity is about O(n^2), but I know there is a faster solution with the usage of skiplist.
#include <iostream>
struct Brick
{
int begin;
int end;
int height = 1;
};
bool DoOverlap(Brick a, Brick b)
{
return (a.end > b.begin && a.begin < b.end)
}
int theHighest(Brick bricks[], int n)
{
int height = 1;
for (size_t i = 1; i < n; i++)
{
for (size_t j = 0; j < i; j++)
{
if (bricks[i].height <= bricks[j].height && DoOverlap(bricks[i], bricks[j]))
{
bricks[i].height = bricks[j].height + 1;
if (bricks[i].height > height)
height = bricks[i].height;
}
}
}
return height;
}
That's an example drawing of created construction.
You can simply use 2 pointers after sorting the blocks based on their starting positions, if their starting positions match sort them based on their ending positions. Then simply use the 2 pointers to find the maximum height.
Time complexity : O(NlogN)
You can find the demo link here
#include <bits/stdc++.h>
using namespace std;
#define ii pair<int,int>
bool modified_sort(const pair<int,int> &a,
const pair<int,int> &b)
{
if (a.first == b.first) {
return (a.second <b.second);
}
return (a.first <b.first);
}
int main() {
// your code goes here
vector<ii> blocks;
int n; // no of blocks
int a,b;
cin>>n;
for (int i=0;i<n;i++) {
cin>>a>>b;
blocks.push_back(ii(a,b));
}
sort(blocks.begin(), blocks.end(), modified_sort);
int start=0,end=0;
int max_height=0;
while(end<n) {
while(start<end && blocks[start].second <= blocks[end].first)
{
start++;
}
max_height = max(max_height,(end-start+1));
end++;
}
cout<<max_height<<endl;
return 0;
}
Here is a straightforward solution (without skip lists):
Create an array heights
Iterate through the blocks.
For every block
Check the existing entries in the heights array for the positions the current block occupies by iterating over them. Determine their maximum.
Increase the values in the heights array for the current block to the maximum+1 determined in the previous step.
keep score of the maximum tower you have built during the scan.
This problem is isomorphic to a graph traversal. Each interval (block) is a node of the graph. Two blocks are connected by an edge iff their intervals overlap (a stacking possibility). The example you give has graph edges
1 2
1 3
2 3
2 5
and node 4 has no edges
Your highest stack is isomorphic to the longest cycle-free path in the graph. This problem has well-known solutions.
BTW, I don't think your n^2 algorithm works for all orderings of blocks. Try a set of six blocks with one overlap each, such as the intervals [n, n+3] for n in {2, 4, 6, 8, 10, 12}. Feed all permutations of these blocks to your algorithm, and see whether it comes up with a height of 6 for each.
Complexity
I think the highest complexity is likely to be sorting the intervals to accelerate marking the edges. The sort will be O(n log n). Adding edges is O(n d) where d is the mean degree of the graph (and n*d is the number of edges).
I don't have the graph traversal algorithm solidly in mind, but I expect that it's O(d log n).
It looks like you can store your already processed blocks in a skip list. The blocks should be ordered by starting position. Then to find overlapping blocks at each step you should search in this skip list which is O(log n) on average. You find first overlapping block then iterate to next and so on until you meet first non-overlapping block.
So on average you can get O(n * (log(n) + m)) where m - is the mean number of overlapping blocks. In the worst case you still get O(n^2).

Algorithm for selecting closest pairs using Dynamic Programming

I have been trying to solve this problem my professor has given me but couldn't make a proper solution. The following is the problem
Problem:
A rectangular circuit board has two parallel sides with width W between them. There are m terminals on the upper side of the board and n terminals (n < m) on the lower side. Let U1 < U[2] < … < U[m] be the distances from the left end of the board to the m terminals on the upper side, respectively. Let L1 < L[2] < … < L[n] be the distances from the left end of the board to the n terminals on the lower side, respectively. Now, we need to select n terminals from the m terminals on the upper side to be connected to the n terminals on the lower side by n straight line segments, respectively, such that the total length of the n line segments is minimized. The following figure illustrates the problem for m = 8 and n = 4.
(a) Prove that, in an optimal solution, any two line segments will not intersect.
(b) Design an O(mn) dynamic programming algorithm to solve this minimization problem. You need to define sub-problems, show the inductive formula, initial conditions, and a pseudocode. You can use d(i, j) to denote the distance between U[i] and L[j], 1 ≤ i ≤ m, 1 ≤ j ≤ n. (The calculation of d(i, j) = ) can be omitted.
My Approach:
For the above problem, my approach was first to make a matrix d(i,j) where i are the terminals on the bottom and j are the terminals on the top. d(i,j) has all the distances from any two circuits.Then iterating through each row I will find the smallest distance and mark the respective terminal. But I am not sure this would work if the top circuits are all to the extreme right of the side. So can anyone provide me with a better approach.
I have written a recursive Dynamic Programming solution that uses memoisation, the complexity is O(mn), here at each recursive level we can either choose to join the current point defined in the U[] array with the point defined in the L[] array, or we can move forward without doing so:
#include<iostream>
#define INF 1e9
using namespace std;
int n, m, d[100][100], dp[100][100];
int solve(int idx1, int idx2){
if(idx1 > m){
if(idx2 < n) return INF;
else return 0;
}
if(idx2 > n) return 0;
if(dp[idx1][idx2] != -1) return dp[idx1][idx2];
int v1, v2;
//include current
v1 = solve(idx1 + 1, idx2 + 1) + d[idx1][idx2];
//do not include current
v2 = solve(idx1 + 1, idx2);
return dp[idx1][idx2] = min(v1, v2);
}
int main(){
//enter the the distances
for(int i = 0;i < 100;i++) for(int j = 0;j < 100;j++) dp[i][j] = -1;
cout << solve(1, 1) << endl;
return 0;
}
For the part (a) of your question, let us assume that 2 line segments do intersect, then we cannot have an optimal solution because if we just swapped the 2 end points of the line segments defined by the L[] array then the distance would reduce, hence giving us a better solution.

Find Maximum Distance between two points

Yesterday, I appeared in an interview. I was stuck in one of the question, and I am asking here the same.
There is an array given that shows points on x-axis, there are N points. and M coins also given.
Remember N >= M
You have to maximize the minimum distance between any two points.
Example: Array : 1 3 6 12
3 coins are given.
If you put coins on 1, 3, 6 points Then distance between coins are : 2, 3
So it gives 2 as answer
But we can improve further
If we put coins at 1, 6, 12 points.
Then distances are 5, 6
So correct answer is : 5
Please help me because I am totally stuck in this question.
Here's my O(N2) approach to this. First, generate all possible distances;
int dist[1000000][3], ndist = 0;
for(int i = 0; i < n; i ++) {
for(int j = i + 1; j < n; j ++) {
dist[ndist][0] = abs(points[i] - points[j]);
dist[ndist][1] = i; //save the first point
dist[ndist][2] = j; //save the second point
}
}
Now sort the distances in decreasing order:
sort(dist, dist + ndist, cmp);
Where cmp is:
bool cmp(int x[], int y[]) {
return (x[0] > y[0]);
}
Sweep through the array, adding points as long as as you don't have m points chosen:
int chosen = 0;
int ans;
for(int i = 0; i < ndist; i ++) {
int whatadd = (!visited[dist[i][1]]) + (!visited[dist[i][2]); //how many new points we'll get if we take this one
if(chosen + whatadd > m) {
break;
}
visited[dist[i][1]] = 1;
visited[dist[i][2]] = 1;
chosen += whatadd;
ans = dist[i][0]; //ans is the last dist[i][0] chosen, since they're sorted in decreasing order
}
if(chosen != m) {
//you need at most one extra point, choose the optimal available one
//left as an exercise :)
}
I hope that helped!
You could use a gready algorithm: you choose the first and last point of the ordered series (as this is the largest possible distance). Then you choose the point closest to the average of them (as any other answer will give a smaller distance in one side), than choose the larger partition. Then repeat it as many times as you need.
You have to use Dynamic Programming. Because, you need an optimum answer.
Your problem is similar to "Change -making of the coin" problem. Like the problem, you have no of coins and you want to find minimum distance.(Instead of minimum no of coins).
You can read following link: Change Coin problem & Dynamic Programming

How to compute optimal paths for traveling salesman bitonic tour?

UPDATED
After more reading, the solution can be given with the following recurrence relation:
(a) When i = 1 and j = 2, l(i; j) = dist(pi; pj )
(b) When i < j - 1; l(i; j) = l(i; j - 1) + dist(pj-1; pj)
(c) When i = j - 1 and j > 2, min 1<=k<i (l(k; i) + dist(pk; pj ))
This is now starting to make sense, except for part C. How would I go about determining the minimum value k? I suppose it means you can iterate through all possible k values and just store the minimum result of ( l(k,i) + dist(pk,pj)?
Yes, definitely a problem I was studying at school. We are studying bitonic tours for the traveling salesman problem.
Anyway, say I have 5 vertices {0,1,2,3,4}. I know my first step is to sort these in order of increasing x-coordinates. From there, I am a bit confused on how this would be done with dynamic programming.
I am reading that I should scan the list of sorted nodes, and maintain optimal paths for both parts (initial path and the return path). I am confused as to how I will calculate these optimal paths. For instance, how will I know if I should include a given node in the initial path or the return path, since it cannot be in both (except for the endpoints). Thinking back to Fibonacci in dynamic programming, you basically start with your base case and work your way forward. I guess what I am asking is how would I get started with the bitonic traveling salesman problem?
For something like the Fibonacci numbers, a dynamic programming approached is quite clear. However, I don't know if I am just being dense or what but I am quite confused trying to wrap my head around this problem.
Thanks for looking!
NOTE: I am not looking for complete solutions, but at least some good tips to get my started. For example, if this were the Fibonacci problem, one could illustrate how the first few numbers are calculated. Please let me know how I can improve the question as well.
Clarification on your algorithm.
The l(i,j) recursive function should compute the minimum distance of a bitonic tour i -> 1 -> j visiting all nodes that are smaller than i. So, the solution to the initial problem will be l(n,n)!
Important notes:
we can assume that the nodes are ordered by their x coordinate and labeled accordingly (p1.x < p2.x < p3.x ... < pn.x). It they weren't ordered, we could sort them in O(nlogn) time.
l(i,j) = l(j,i). The reason is that in the lhs, we have a i ->...-> 1 -> ... -> j tour which is optimal. However traversing this route backward will give us the same distance, and won't broke bitonic property.
Now the easy cases (note the changes!):
(a) When i = 1 and j = 2, l(i; j) = dist(pi; pj ) = dist(1,2)
Here we have the following tour : 1->1->...->2. Trivially this is equivalent to the length of the path 1->...->2. Since points are ordered by their .x coordinate, there is no point between 1 and 2, so the straight line connecting them will be the optimal one. ( Choosing any number of other points to visit before 2 would result in a longer path! )
(b) When i < j - 1; l(i; j) = l(i; j - 1) + dist(pj-1; pj)
In this case, j-1 must be on the part of the path 1 -> ... -> j, because the part i -> ... -> 1 can not contain nodes with an index bigger than i. Because all nodes in the path 1 -> ... -> j are in increasing order of index, there can be none between j-1 and j. So, this is equivalent to the tour: i -> ... -> 1 -> .... -> j-1 -> j, which is equivalent to l(i,j-1) + dist(pj-1,pj)!
Anf finally the interesting part comes:
(c) When i = j - 1 or i = j, min 1<=k<i (l(k; i) + dist(pk; pj ))
Here we know that we have to get from i to 1, but there is no clue on the backward sweep! The key idea here is that we must think of the node just before j on our backward route. It may be any of the nodes from 1 to j-1! Let us assume that this node is k.
Now we have a tour: i -> ... -> 1 -> .... -> k -> j, right? The cost of this tour is l(i,k) + dist(pk,pj).
Hope you got it.
Implementation.
You will need a 2-dimensional array say BT[1..n][1..n]. Let i be the row index, j be the column index. How should we fill in this table?
In the first row we know BT[1][1] = 0, BT[1][2] = d(1,2), so we have only i,j indexes left that fall into the (b) category.
In the remainin rows, we fill the elements from the diagonal till the end.
Here is a sample C++ code (not tested):
void ComputeBitonicTSPCost( const std::vector< std::vector<int> >& dist, int* opt ) {
int n = dist.size();
std::vector< std::vector< int > > BT;
BT.resize(n);
for ( int i = 0; i < n; ++i )
BT.at(i).resize(n);
BT.at(0).at(0) = 0; // p1 to p1 bitonic distance is 0
BT.at(0).at(1) = dist.at(0).at(1); // p1 to p2 bitonic distance is d(2,1)
// fill the first row
for ( int j = 2; j < n; ++j )
BT.at(0).at(j) = BT.at(0).at(j-1) + dist.at(j-1).at(j);
// fill the remaining rows
int temp, min;
for ( int i = 1; i < n; ++i ) {
for ( int j = i; j < n; ++j ) {
BT.at(i).at(j) = -1;
min = std::numeric_limits<int>::max();
if ( i == j || i == j -1 ) {
for( int k = 0; k < i; ++k ) {
temp = BT.at(k).at(i) + dist.at(k).at(j);
min = ( temp < min ) ? temp : min;
}
BT.at(i).at(j) = min;
} else {
BT.at(i).at(j) = BT.at(i).at(j-1) + dist.at(j-1).at(j);
}
}
}
*opt = BT.at(n-1).at(n-1);
}
Okay, the key notions in a dynamic programming solution are:
you pre-compute smaller problems
you have a rule to let you combine smaller problems to find solutions for bigger problems
you have a known property of the problems that let's you prove the solution is really optimal under some measure of optimality. (In this case, shortest.)
The essential property of a bitonic tour is that a vertical line in the coordinate system crosses a side of the closed polygon at most twice. So, what is a bitonic tour of exactly two points? Clearly, any two points form a (degenerate) bitonic tour. Three points have two bitonic tours ("clockwise" and "counterclockwise").
Now, how can you pre-compute the various smaller bitonic tours and combine them until you have all points included and still have a bitonic tour?
Okay, you're on the righ track with your update. But now, in a dynamic programming solution, what you do with work it bottom-up: pre-compute and memoize (not "memorize") the optimal subproblems.

Resources