Programming Forums
User Name Password Register
 

RSS Feed
FORUM INDEX | TODAY'S POSTS | UNANSWERED THREADS | ADVANCED SEARCH

Reply
 
Thread Tools Display Modes
Old Jul 1st, 2008, 12:45 PM   #1
Sane
Programming Guru
 
Sane's Avatar
 
Join Date: Apr 2005
Location: Waterloo, Ontario
Posts: 2,035
Rep Power: 6 Sane will become famous soon enough
Send a message via MSN to Sane
SMAC #2 [06-08] - Escalator Troubles (Senior)

Attached to this post is the test data for escalator troubles.


Escalator Troubles (Senior)

Escalators was a classic problem in graph theory.

A floor represents a "vertex" (or node), and an escalator represents a "directed edge". All the floors and escalators together form a map, referred to as a "directed graph" (or Digraph).

Given a directed graph, test whether or not every vertex is reachable from every other vertex.

Titanium emerged as the victor for this problem with the only perfect. Considering it was a tough problem, everyone who submitted also did very well.

C Syntax (Toggle Plain Text)
  1. /*
  2.  * SMAC #2
  3.  * titaniumdecoy
  4.  */
  5. #include <stdio.h>
  6. #include <stdlib.h>
  7. #include <string.h>
  8.  
  9. typedef struct escalator
  10. {
  11. int to_floor;
  12. struct escalator *next;
  13. }
  14. escalator_t;
  15.  
  16. void build_escalator(escalator_t **es, int from, int to)
  17. {
  18. escalator_t *new_es = NULL;
  19.  
  20. new_es = malloc(sizeof(escalator_t));
  21. new_es->to_floor = to;
  22.  
  23. new_es->next = es[from];
  24. es[from] = new_es;
  25. }
  26.  
  27. void can_reach_all_floors(escalator_t **es, int n, int *visited, int x)
  28. {
  29. escalator_t *es_ptr = es[x];
  30.  
  31. while (es_ptr != NULL) {
  32. if (visited[es_ptr->to_floor] == 0) {
  33. visited[es_ptr->to_floor] = 1;
  34. can_reach_all_floors(es, n, visited, es_ptr->to_floor);
  35. }
  36. es_ptr = es_ptr->next;
  37. }
  38. }
  39.  
  40. int main()
  41. {
  42. int i = 0, j = 0;
  43. int n = 0, e = 0;
  44. int from = 0, to = 0;
  45. escalator_t **es;
  46. int havoc = 0;
  47.  
  48. freopen("escalator.in", "r", stdin);
  49. freopen("escalator.out", "w", stdout);
  50.  
  51. scanf("%d %d", &n, &e);
  52.  
  53. es = calloc(sizeof(escalator_t *), n);
  54.  
  55. for (i = 0; i < e; i++)
  56. {
  57. scanf("%d %d", &from, &to);
  58. build_escalator(es, from - 1, to - 1);
  59. }
  60.  
  61. int *visited = calloc(sizeof(int), n);
  62.  
  63. for (i = 0; i < n; i++) {
  64. can_reach_all_floors(es, n, visited, i);
  65.  
  66. for (j = 0; j < n; j++)
  67. if (visited[j] == 0) {
  68. havoc = 1;
  69. goto end;
  70. }
  71.  
  72. for (j = 0; j < n; j++)
  73. visited[j] = 0;
  74. }
  75.  
  76. end:
  77. if (havoc)
  78. printf("Bob is going to get fired!\n");
  79. else
  80. printf("Vladimir's plan failed.\n");
  81.  
  82. return 0;
  83. }


I have come up with 4 different solutions to this problem.

1 is fast.
1 is very fast.
1 is intuitive.
1 is an easy to code DP algorithm.

All of them run in time since an "n" of 100 is very small. I will start from the simplest and work towards the most difficult solution.


------------------


Solution #1 (Slowest)


This was one of the expected solutions, and is the simplest to understand.

The idea is to take every single pair of vertecies and test whether or not 'a' can reach 'b'.

For example, if n = 3,

(a,b)
-----
(1,2)
(1,3)
(2,1)
(2,3)
(3,1)
(3,2)

For each pair, you start at 'a', and try to find a route to 'b'.

If there's ever a pair that does not work, Bob is getting fired.

If every pair is successfull, Vladimir's plan has failed.

A way to find a route from 'a' to 'b' is by using recursion, with some very similar ideas to the solution from Junior's "Trapped".

You recursively branch off to each possible path (ride every possible escalator from a), and then make sure you don't try the same floor twice. This repeats until you find b, or run out of floors to try.

Marking a floor as visited is done in a "visited" array of 1s and 0s.

This fundamental algorithm to search a graph is called a Depth-First Search (DFS).


The reason this solution is very slow is because work is being repeated. In our search from a to b, we may encounter verticies c, d, e, and f. This tells us that 'a' can reach c, d, e, and f. Yet later, we still check for a path from a to c, a to d, a to e, and a to f. This repeated work can be eliminated by being a little smarter (see the 2nd and 3rd solutions).

C Syntax (Toggle Plain Text)
  1. #include <stdio.h>
  2. #define MAX 101
  3.  
  4. int adj[MAX][MAX] = {0};
  5. int visited[MAX];
  6. int n;
  7.  
  8. int find(int i, int dest) {
  9. int j;
  10.  
  11. if(i == dest) return 1;
  12. if(visited[i]) return 0;
  13. visited[i] = 1;
  14.  
  15. for(j=0; j<n; j++) if(adj[i][j]) if (find(j, dest)) return 1;
  16. return 0;
  17. }
  18.  
  19. int main() {
  20. int i, j, u, v;
  21.  
  22. freopen("escalator.in", "rt", stdin);
  23. freopen("escalator.out", "wt", stdout);
  24.  
  25. scanf("%d %d", &n, &i);
  26. while(i--) {
  27. scanf("%d %d", &u, &v); u--; v--;
  28. adj[u][v] = 1;
  29. }
  30.  
  31. for(i=0; i<n; i++) {
  32. for(j=0; j<n; j++) {
  33. memset(visited, 0, sizeof(visited));
  34. if(!find(i,j)) {
  35. printf("Bob is going to get fired!\n");
  36. return 0;
  37. }
  38. }
  39. }
  40.  
  41. printf("Vladimir's plan failed.\n");
  42. return 0;
  43. }



------------------


Solution #2 (Fast)

This solution is very similar to #1, except we make one (rather significant) improvement.

Instead of checking every pair, we only need to count how many vertecies are reachable from a. If 'a' can reach 'n' vertecies (including itself), then it can reach every floor. This eliminates the need to check all other combinations involving 'a', thus the number of depth-first searches becomes linear rather than quadratic.

This was the "ideal" solution to Escalators.


C Syntax (Toggle Plain Text)
  1. #include <stdio.h>
  2. #define MAX 101
  3.  
  4. int adj[MAX][MAX] = {0};
  5. int visited[MAX];
  6. int n;
  7.  
  8. int count(int i) {
  9. int j, s=0;
  10.  
  11. if(visited[i]) return 0;
  12. visited[i] = 1;
  13.  
  14. for(j=0; j<n; j++) if(adj[i][j]) s += count(j);
  15. return s+1;
  16. }
  17.  
  18. int main() {
  19. int i, j, u, v;
  20.  
  21. freopen("escalator.in", "rt", stdin);
  22. freopen("escalator.out", "wt", stdout);
  23.  
  24. scanf("%d %d", &n, &i);
  25. while(i--) {
  26. scanf("%d %d", &u, &v); u--; v--;
  27. adj[u][v] = 1;
  28. }
  29.  
  30. for(i=0; i<n; i++) {
  31. memset(visited, 0, sizeof(visited));
  32. if(count(i) < n) {
  33. printf("Bob is going to get fired!\n");
  34. return 0;
  35. }
  36. }
  37.  
  38. printf("Vladimir's plan failed.\n");
  39. return 0;
  40. }


------------------


Solution #3 (Very Fast)

There is one last improvement that can be made to solutions #1 and #2 to come to the fastest known algorithm.

It only requires one observation, which when heard, is almost astoundingly obvious:

If 'a' can reach every node, and every node can reach 'a', then every node can reach every node!


As an algorithm:

Step 1) Check to see if 'a' can reach every node
Step 2) Check to see if every node can reach 'a'

Done!


Why is this true? Say you want to get from 'c' to 'b'. If every node can reach 'a', then 'c' can reach 'a'. And if 'a' can reach every node, then 'a' can reach 'b'. So 'c' goes to 'a' goes to 'b'. This may not be the fastest route, but the efficiency of the route is completely irrelevant in this problem.

But is the inverse always true? If every node is reachable from every other node, 'a' must be able to reach every node and every node must be able to reach 'a'. Therefore, the algorithm must be correct.


Checking if 'a' can reach every node uses the same DFS from solution #2.

Checking if every node can reach 'a' requires a little ingenuity. There are a couple ways, but the easiest way is to flip the direction of the edges and run another DFS. By flipping the edges, we travel the graph backwards from 'a' and reveal which nodes can reach 'a'.


C Syntax (Toggle Plain Text)
  1. #include <stdio.h>
  2. #define MAX 101
  3.  
  4. int forwadj[MAX][MAX] = {0};
  5. int backadj[MAX][MAX] = {0};
  6. int visited[MAX];
  7. int n;
  8.  
  9. int count(int i, int adjmatrix[MAX][MAX]) {
  10. int j, s=0;
  11.  
  12. if(visited[i]) return 0;
  13. visited[i] = 1;
  14.  
  15. for(j=0; j<n; j++) if(adjmatrix[i][j]) s += count(j, adjmatrix);
  16. return s+1;
  17. }
  18.  
  19. int main() {
  20. int i, j, u, v;
  21.  
  22. freopen("escalator.in", "rt", stdin);
  23. freopen("escalator.out", "wt", stdout);
  24.  
  25. scanf("%d %d", &n, &i);
  26. while(i--) {
  27. scanf("%d %d", &u, &v); u--; v--;
  28. forwadj[u][v] = backadj[v][u] = 1;
  29. }
  30.  
  31. memset(visited, 0, sizeof(visited));
  32. i = count(0, forwadj); // Traverse the Digraph forwards
  33. memset(visited, 0, sizeof(visited));
  34. j = count(0, backadj); // Traverse the Digraph backwards
  35.  
  36. if(i<n || j<n) printf("Bob is going to get fired!\n");
  37. else printf("Vladimir's plan failed.\n");
  38.  
  39. return 0;
  40. }


------------------


Solution #4 (DP)

There is another solution that uses Dynamic Programming to find the answer very elegantly.

It is called the Floyd-Warshall algorithm, which can be modified to check for transitive closure.


You initially consider every pair of nodes 'i' and 'j', which may or may not have an edge between them.

Then you take another node, called 'k', and introduce it to each pair. For each (i,j) pair, if (i,k) and (k,j) are reachable, then so is (i,j). Thus, an edge can be drawn from 'i' to 'j'.

This essentially built an updated graph where an edge between i and j represents closure between i and j with or without k. And each introduction of a new k updates the graph according to what can now use k.

Once every possible k has been introduced, an edge drawn between i,j means i can reach j if it uses some combination of the k intermediate nodes.

Thus, after a run of Warshall's algorithm, if there is any (i,j) pair which does not have an edge between them, Bob is getting fired.


C Syntax (Toggle Plain Text)
  1. #include <stdio.h>
  2. #define MAX 101
  3.  
  4. int adj[MAX][MAX] = {0};
  5. int n;
  6.  
  7. int main() {
  8. int i, j, k;
  9.  
  10. freopen("escalator.in", "rt", stdin);
  11. freopen("escalator.out", "wt", stdout);
  12.  
  13. scanf("%d %d", &n, &k);
  14. while(k--) {
  15. scanf("%d %d", &i, &j); i--; j--;
  16. adj[i][j] = 1;
  17. }
  18.  
  19. for(k=0; k<n; k++)
  20. for(i=0; i<n; i++)
  21. for(j=0; j<n; j++)
  22. adj[i][j] |= adj[i][k] && adj[k][j];
  23.  
  24. for(i=0; i<n; i++)
  25. for(j=0; j<n; j++)
  26. if(!adj[i][j]) {
  27. printf("Bob is going to get fired!\n");
  28. return 0;
  29. }
  30.  
  31. printf("Vladimir's plan failed.\n");
  32. return 0;
  33. }
Attached Files
File Type: zip escalator.zip (27.3 KB, 1 views)
__________________
Looking for tough programming challenges? Try participating in Sane's Monthly Algorithms Challenges!
Composing Techno is a little side hobby of mine. Techno by DJ Sane. All free for download.
Sane is offline   Reply With Quote
Old Jul 1st, 2008, 5:09 PM   #2
titaniumdecoy
Expert Programmer
 
titaniumdecoy's Avatar
 
Join Date: Nov 2005
Posts: 909
Rep Power: 3 titaniumdecoy is on a distinguished road
Send a message via AIM to titaniumdecoy
Re: SMAC #2 [06-08] - Escalator Troubles (Senior)

Sane, can you explain Solution #2 (Fast) a little more? As I understand your explanation, if floor A can reach every other floor, then it doesn't matter which floors can reach A. But that doesn't make sense, since you could have the following: AA, AB, BB. Then you cannot reach floor A from floor B.

What am I missing?
titaniumdecoy is offline   Reply With Quote
Old Jul 1st, 2008, 11:48 PM   #3
Sane
Programming Guru
 
Sane's Avatar
 
Join Date: Apr 2005
Location: Waterloo, Ontario
Posts: 2,035
Rep Power: 6 Sane will become famous soon enough
Send a message via MSN to Sane
Re: SMAC #2 [06-08] - Escalator Troubles (Senior)

The solution I describe in #2 is your solution. I think I just confused you by how I said "combinations involving 'a'". I really meant all of the pairs starting with 'a'. But in short, just refer to your solution, as it's the same as my #2 solution.
__________________
Looking for tough programming challenges? Try participating in Sane's Monthly Algorithms Challenges!
Composing Techno is a little side hobby of mine. Techno by DJ Sane. All free for download.
Sane is offline   Reply With Quote
Old Jul 2nd, 2008, 12:20 AM   #4
titaniumdecoy
Expert Programmer
 
titaniumdecoy's Avatar
 
Join Date: Nov 2005
Posts: 909
Rep Power: 3 titaniumdecoy is on a distinguished road
Send a message via AIM to titaniumdecoy
Re: SMAC #2 [06-08] - Escalator Troubles (Senior)

I am fairly certain my solution is #1; notice that the can_reach_all_floors function is recursive, which makes it O(N*E). I will take a closer look at your explanation.
titaniumdecoy is offline   Reply With Quote
Old Jul 2nd, 2008, 12:26 AM   #5
Sane
Programming Guru
 
Sane's Avatar
 
Join Date: Apr 2005
Location: Waterloo, Ontario
Posts: 2,035
Rep Power: 6 Sane will become famous soon enough
Send a message via MSN to Sane
Re: SMAC #2 [06-08] - Escalator Troubles (Senior)

Your "canreachallfloors" function, by name, is solution #2. Solution #2 checks to see if every node can reach all floors. That's all.

Solution #1 checks to see if every pair of nodes can reach each other. That's probably the most obvious thing to try, but also the slowest. No one did solution #1.
__________________
Looking for tough programming challenges? Try participating in Sane's Monthly Algorithms Challenges!
Composing Techno is a little side hobby of mine. Techno by DJ Sane. All free for download.
Sane is offline   Reply With Quote
Old Jul 2nd, 2008, 12:29 AM   #6
titaniumdecoy
Expert Programmer
 
titaniumdecoy's Avatar
 
Join Date: Nov 2005
Posts: 909
Rep Power: 3 titaniumdecoy is on a distinguished road
Send a message via AIM to titaniumdecoy
Re: SMAC #2 [06-08] - Escalator Troubles (Senior)

Ah, I misread your analysis. So Solution #2 itself is not linear, but the number of depth-first searches necessary is linear. That makes sense.
titaniumdecoy is offline   Reply With Quote
Reply

Bookmarks

Tags
smac senior solutions

« Previous Thread in Forum | Next Thread in Forum »

Currently Active Users Viewing This Thread: 1 (0 members and 1 guests)
 
Thread Tools
Display Modes

Posting Rules
You may not post new threads
You may not post replies
You may not post attachments
You may not edit your posts

BB code is On
Smilies are On
[IMG] code is On
HTML code is Off
Forum Jump

Similar Threads
Thread Thread Starter Forum Replies Last Post
SMAC #1 [05-08] - Results Sane Software Design and Algorithms 6 Jun 2nd, 2008 5:56 PM
SMAC #1 [05-08] - Ranged Reach (Senior) Sane Software Design and Algorithms 0 Jun 2nd, 2008 5:21 PM
SMAC #1 [05-08] - Hurtles (Senior) Sane Software Design and Algorithms 0 Jun 1st, 2008 4:59 PM
Ideas for my Senior Project? joshbax88 Java 1 Apr 26th, 2008 6:57 AM
Senior Capstone-Spying Screen Saver Hounder Project Ideas 23 Dec 16th, 2005 8:00 PM




DaniWeb IT Discussion Community
All times are GMT -5. The time now is 6:12 AM.

Powered by vBulletin® Version 3.7.0, Copyright ©2000 - 2008, Jelsoft Enterprises Ltd.
Copyright ©2007 DaniWeb® LLC