dragon7
dragon7

Reputation: 1123

Find all puddles on the square (algorithm)

The problem is defined as follows: You're given a square. The square is lined with flat flagstones size 1m x 1m. Grass surround the square. Flagstones may be at different height. It starts raining. Determine where puddles will be created and compute how much water will contain. Water doesn't flow through the corners. In any area of ​​grass can soak any volume of water at any time.

Input:

width height

width*height non-negative numbers describing a height of each flagstone over grass level.

Output:

Volume of water from puddles.

width*height signs describing places where puddles will be created and places won't.

. - no puddle

# - puddle

Examples

Input:

8 8
0 0 0 0 0 1 0 0 
0 1 1 1 0 1 0 0
0 1 0 2 1 2 4 5 
0 1 1 2 0 2 4 5 
0 3 3 3 3 3 3 4 
0 3 0 1 2 0 3 4 
0 3 3 3 3 3 3 0 
0 0 0 0 0 0 0 0 

Output:

11
........
........
..#.....
....#...
........
..####..
........
........

Input:

16 16
8 0 1 0 0 0 0 2 2 4 3 4 5 0 0 2
6 2 0 5 2 0 0 2 0 1 0 3 1 2 1 2
7 2 5 4 5 2 2 1 3 6 2 0 8 0 3 2
2 5 3 3 0 1 0 3 3 0 2 0 3 0 1 1
1 0 1 4 1 1 2 0 3 1 1 0 1 1 2 0
2 6 2 0 0 3 5 5 4 3 0 4 2 2 2 1
4 2 0 0 0 1 1 2 1 2 1 0 4 0 5 1
2 0 2 0 5 0 1 1 2 0 7 5 1 0 4 3
13 6 6 0 10 8 10 5 17 6 4 0 12 5 7 6
7 3 0 2 5 3 8 0 3 6 1 4 2 3 0 3
8 0 6 1 2 2 6 3 7 6 4 0 1 4 2 1
3 5 3 0 0 4 4 1 4 0 3 2 0 0 1 0
13 3 6 0 7 5 3 2 21 8 13 3 5 0 13 7
3 5 6 2 2 2 0 2 5 0 7 0 1 3 7 5
7 4 5 3 4 5 2 0 23 9 10 5 9 7 9 8
11 5 7 7 9 7 1 0 17 13 7 10 6 5 8 10

Output:

103
................
..#.....###.#...
.......#...#.#..
....###..#.#.#..
.#..##.#...#....
...##.....#.....
..#####.#..#.#..
.#.#.###.#..##..
...#.......#....
..#....#..#...#.
.#.#.......#....
...##..#.#..##..
.#.#.........#..
......#..#.##...
.#..............
................

I tried different ways. Floodfill from max value, then from min value, but it's not working for every input or require code complication. Any ideas?

I'm interesting algorithm with complexity O(n^2) or o(n^3).

Upvotes: 1

Views: 997

Answers (3)

Mohammad Azim
Mohammad Azim

Reputation: 2933

This can be solved using Dijkstra's algorithm for finding path that has least resistance to water flow for each point on the field and computing the max on-the-go to calculate the water held at each point.

#include <iostream>
#include <vector>
#include <tuple>
#include <unordered_map>
#include <queue>

using namespace std;

struct Point {
    int x;
    int y;
    int elevation;
    vector<tuple<int, int>> path;

    Point(int x, int y, int level) : x(x), y(y), elevation(level)
    {
    }
};

struct LeastLevel {
    bool operator()(const Point l, const Point r) const {
        return l.elevation > r.elevation;
    }
};

// Directions left, up, right, down
int directions[4][2] = { {-1, 0}, {0, -1}, {1, 0}, {0, 1} };

int find_drain(int* field, int w, int h, int x, int y, int *cache) {
    // Edges have same drain level
    if (x == 0 || x == w - 1 || y == 0 || y == h - 1)
        return field[w * y + x];

    priority_queue<Point, vector<Point>, LeastLevel> q;
    
    // insert nieghbours with their water level
    for (int d = 0; d < 4; ++d) {
        int _x = x + directions[d][0], _y = y + directions[d][1];
        if (_x < 0 || _x >= w || _y < 0 || _y >= h)
            continue;
        q.emplace(_x, _y, field[w * _y + _x]);
    }

    int* visited = new int[w * h] {};
    visited[w * y + x] = 1;
    int _min = INT_MAX;
    while (!q.empty()) {
        Point p = q.top();
        q.pop();
        visited[w * p.y + p.x] = 1;

        // Optimisation: Use cached result to avoid retracing
        if (cache[w * p.y + p.x] != -1) {
            auto _max = max(p.elevation, cache[w * p.y + p.x]);
            _min = min(_min, _max);
            if (_max == _min)
                break;
            else
                continue;
        }
        // Reached the edge, check if this was a min resistant path
        else
        if (p.x == 0 || p.x == w - 1 || p.y == 0 || p.y == h - 1) {
            auto _max = max(p.elevation, field[w * p.y + p.x]);
            _min = min(_min, _max);
            break;
        }
        // for each neighbour find more neighbours
        for (int d = 0; d < 4; ++d) {
            int _x = p.x + directions[d][0], _y = p.y + directions[d][1];
            if (_x < 0 || _x >= w || _y < 0 || _y >= h || visited[w * _y + _x])
                continue;
            
            // the water level of each neighbour max of this point and their level
            q.emplace(_x, _y, max(p.elevation, field[w * _y + _x]));
        }
        
    }
    delete[]visited;
    return _min;
}

int find_puddles(int* field, int w, int h) {
    int total = 0;

    int* cache = new int[w * h];
    for (int i = 0; i < w * h; ++i) {
        cache[i] = -1;
    }
    for (int y = 0; y < h; ++y) {
        for (int x = 0; x < w; ++x) {
            auto elevation = field[w * y + x];
            auto max_elevation = find_drain(field, w, h, x, y, cache);
            if (max_elevation > elevation) {
                total += max_elevation - elevation;
            }
            cache[w * y + x] = max(max_elevation, elevation);
        }
    }
    delete[] cache;
    return total;
}

int main() {
    int h, w;
    cin >> h;
    cin >> w;
    int* field = new int[h * w];
    for (int i = 0; i < h; ++i) {
        for (int j = 0; j < w; ++j) {
            cin >> field[i * w + j];
        }
    }
    printf("Water volume %d\n", find_puddles(field, w, h));

    return 0;
}

Upvotes: 0

MobA11y
MobA11y

Reputation: 18860

This seems to be working nicely. The idea is it is a recursive function, that checks to see if there is an "outward flow" that will allow it to escape to the edge. If the values that do no have such an escape will puddle. I tested it on your two input files and it works quite nicely. I copied the output for these two files for you. Pardon my nasty use of global variables and what not, I figured it was the concept behind the algorithm that mattered, not good style :)

    #include <fstream>
#include <iostream>
#include <vector>
using namespace std;

int SIZE_X;
int SIZE_Y;


bool **result;
int **INPUT;


bool flowToEdge(int x, int y, int value, bool* visited) {
    if(x < 0 || x == SIZE_X || y < 0 || y == SIZE_Y) return true;
    if(visited[(x * SIZE_X) + y]) return false;
    if(value < INPUT[x][y]) return false;

    visited[(x * SIZE_X) + y] = true;

    bool left = false;
    bool right = false;
    bool up = false;
    bool down = false;


    left = flowToEdge(x-1, y, value, visited);
    right = flowToEdge(x+1, y, value, visited);
    up = flowToEdge(x, y+1, value, visited);
    down = flowToEdge(x, y-1, value, visited);


    return (left || up || down || right);
}

int main() {

    ifstream myReadFile;
    myReadFile.open("test.txt");

    myReadFile >> SIZE_X;
    myReadFile >> SIZE_Y;

    INPUT = new int*[SIZE_X];
    result = new bool*[SIZE_X];
    for(int i = 0; i < SIZE_X; i++) {
        INPUT[i] = new int[SIZE_Y];
        result[i] = new bool[SIZE_Y];
        for(int j = 0; j < SIZE_Y; j++) {
            int someInt;
            myReadFile >> someInt;
            INPUT[i][j] = someInt;
            result[i][j] = false;
        }
    }


    for(int i = 0; i < SIZE_X; i++) {
        for(int j = 0; j < SIZE_Y; j++) {
            bool visited[SIZE_X][SIZE_Y];
            for(int k = 0; k < SIZE_X; k++)//You can avoid this looping by using maps with pairs of coordinates instead
                for(int l = 0; l < SIZE_Y; l++)
                    visited[k][l] = 0;

            result[i][j] = flowToEdge(i,j, INPUT[i][j], &visited[0][0]);
        }
    }

    for(int i = 0; i < SIZE_X; i++) {
        cout << endl;
        for(int j = 0; j < SIZE_Y; j++)
            cout << result[i][j];
    }
    cout << endl;
}

The 16 by 16 file:

1111111111111111
1101111100010111
1111111011101011
1111000110101011
1011001011101111
1110011111011111
1100000101101011
1010100010110011
1110111111101111
1101101011011101
1010111111101111
1110011010110011
1010111111111011
1111110110100111
1011111111111111
1111111111111111

The 8 by 8 file

11111111
11111111
11011111
11110111
11111111
11000011
11111111
11111111

You could optimize this algorithm easily and considerably by doing several things. A: return true immediately upon finding a route would speed it up considerably. You could also connect it globally to the current set of results so that any given point would only have to find a flow point to an already known flow point, and not all the way to the edge.

The work involved, each n will have to exam each node. However, with optimizations, we should be able to get this much lower than n^2 for most cases, but it still an n^3 algorithm in the worst case... but creating this would be very difficult(with proper optimization logic... dynamic programming for the win!)

EDIT:

The modified code works for the following circumstances:

8 8
1 1 1 1 1 1 1 1
1 0 0 0 0 0 0 1
1 0 1 1 1 1 0 1
1 0 1 0 0 1 0 1
1 0 1 1 0 1 0 1
1 0 1 1 0 1 0 1
1 0 0 0 0 1 0 1
1 1 1 1 1 1 1 1

And these are the results:

11111111
10000001
10111101
10100101
10110101
10110101
10000101
11111111

Now when we remove that 1 at the bottom we want to see no puddling.

8 8
1 1 1 1 1 1 1 1
1 0 0 0 0 0 0 1
1 0 1 1 1 1 0 1
1 0 1 0 0 1 0 1
1 0 1 1 0 1 0 1
1 0 1 1 0 1 0 1
1 0 0 0 0 1 0 1
1 1 1 1 1 1 0 1

And these are the results

1 1 1 1 1 1 1 1
1 1 1 1 1 1 1 1
1 1 1 1 1 1 1 1
1 1 1 1 1 1 1 1
1 1 1 1 1 1 1 1
1 1 1 1 1 1 1 1
1 1 1 1 1 1 1 1
1 1 1 1 1 1 1 1

Upvotes: 1

Peter de Rivaz
Peter de Rivaz

Reputation: 33499

Summary

I would be tempted to try and solve this using a disjoint-set data structure.

The algorithm would be to iterate over all heights in the map performing a floodfill operation at each height.

Details

For each height x (starting at 0)

  1. Connect all flagstones of height x to their neighbours if the neighbour height is <= x (storing connected sets of flagstones in the disjoint set data structure)

  2. Remove any sets that connected to the grass

  3. Mark all flagstones of height x in still remaining sets as being puddles

  4. Add the total count of flagstones in remaining sets to a total t

At the end t gives the total volume of water.

Worked Example

0 0 0 0 0 1 0 0 
0 1 1 1 0 1 0 0
0 1 0 2 1 2 4 5 
0 1 1 2 0 2 4 5 
0 3 3 3 3 3 3 4 
0 3 0 1 2 0 3 4 
0 3 3 3 3 3 3 0 
0 0 0 0 0 0 0 0 

Connect all flagstones of height 0 into sets A,B,C,D,E,F

A A A A A 1 B B 
A 1 1 1 A 1 B B
A 1 C 2 1 2 4 5 
A 1 1 2 D 2 4 5 
A 3 3 3 3 3 3 4 
A 3 E 1 2 F 3 4 
A 3 3 3 3 3 3 A 
A A A A A A A A 

Remove flagstones connecting to the grass, and mark remaining as puddles

          1      
  1 1 1   1
  1 C 2 1 2 4 5     #
  1 1 2 D 2 4 5       #
  3 3 3 3 3 3 4 
  3 E 1 2 F 3 4     #     #
  3 3 3 3 3 3 

Count remaining set size t=4

Connect all of height 1

          G      
  C C C   G
  C C 2 D 2 4 5     #
  C C 2 D 2 4 5       #
  3 3 3 3 3 3 4 
  3 E E 2 F 3 4     #     #
  3 3 3 3 3 3 

Remove flagstones connecting to the grass, and mark remaining as puddles

      2   2 4 5     #
      2   2 4 5       #
  3 3 3 3 3 3 4 
  3 E E 2 F 3 4     # #   #
  3 3 3 3 3 3 

t=4+3=7

Connect all of height 2

      A   B 4 5     #
      A   B 4 5       #
  3 3 3 3 3 3 4 
  3 E E E E 3 4     # #   #
  3 3 3 3 3 3 

Remove flagstones connecting to the grass, and mark remaining as puddles

            4 5     #
            4 5       #
  3 3 3 3 3 3 4 
  3 E E E E 3 4     # # # #
  3 3 3 3 3 3 

t=7+4=11

Connect all of height 3

            4 5     #
            4 5       #
  E E E E E E 4 
  E E E E E E 4     # # # #
  E E E E E E 

Remove flagstones connecting to the grass, and mark remaining as puddles

            4 5     #
            4 5       #
              4 
              4     # # # #

After doing this for heights 4 and 5 nothing will remain.

A preprocessing step to create lists of all locations with each height should mean that the algorithm is close to O(n^2).

Upvotes: 2

Related Questions