-3

I am doing tasks for practicing and improving skills, but I'm having hard time solving this one:

You have a map with size w * h. On each box of this map is wall(flood protection) or nothing. Water can flow in eight directions and flows on each box at the edges of the map. Of course, water can not flood the box where is built flood protection. You will get size of the map, number of walls and position of each wall.

At the beginning the map is empty. Your task is after placing each piece of the wall tell how big area is protected from water.

So, I have code that works. But too slow. Limits are : size of the map w and h (1≤w,h≤2000)
number of walls : n (1≤n≤w×h)

I tried 8-way FloodFill algorithm and then I improved it to 8-way ScanlineFill. It is just too slow, it runs out of time in half of the test entries. I will post my code if you will want it.
So my question is: How can I improve my algorithm to be even faster? Or am I completely wrong with my choice of algorithms and there is a better way? Thanks for your suggestions.

Test entry:

Input:

4 4  //size w and h
10   // number of walls
1 1  //position of wall - first x, second y  coordinate;
1 2
1 3
2 1
2 3
3 1
3 2
3 3
2 2
3 4

Output:

1  //how big are is covered against flood
2
3
4
5
6
7
9
9
10

Explanation of example input: The first 8 part of the wall of flood protect area size 3 x 3. The ninth part has absolutely no effect, because the box (2,2) has already been protected. The tenth part does not conclude any territory and therefore would contribute only its surface(add only 1).

My code:

#include<iostream>

using namespace std;
int **ostrov;
int area;

#define stackSize 16777216
int stack[stackSize];
int stackPointer;
int h, w;


bool pop(int
    &x, int &y)
{
    if (stackPointer > 0)
    {
        int p = stack[stackPointer];
        x = p / h;
        y = p % h;
        stackPointer--;
        return 1;
    }
    else
    {
        return 0;
    }
}

bool push(int x, int y)
{
    if (stackPointer < stackSize - 1)
    {
        stackPointer++;
        stack[stackPointer] = h * x + y;
        return 1;
    }
    else
    {
        return 0;
    }
}

void emptyStack()
{
    int x, y;
    while (pop(x, y));
}

//The scanline floodfill algorithm using our own stack routines, faster
void floodFillScanlineStack(int x, int y, int newColor, int oldColor)
{
    if (oldColor == newColor) return;
    emptyStack();

    int y1;
    bool spanLeft, spanRight, spanDDLeft, spanDDRight, spanDULeft, spanDURight;

    if (!push(x, y)) return;

    while (pop(x, y))
    {
        y1 = y;
        while (y1 >= 0 &&ostrov[x][y1] == oldColor) y1--;
        y1++;
        spanLeft = spanRight = spanDDLeft = spanDDRight = spanDULeft = spanDURight = 0;
        while (y1 < h && ostrov[x][y1] == oldColor)
        {
            if (ostrov[x][y1] == oldColor)
                pocet++;
            ostrov[x][y1] = newColor;
            if (!spanLeft && x > 0 && ostrov[x - 1][y1] == oldColor)
            {
                if (!push(x - 1, y1)) return;
                spanLeft = 1;
            }
            else if (spanLeft && x > 0 && ostrov[x - 1][y1] != oldColor)
            {
                spanLeft = 0;
            }
            if (!spanRight && x < w - 1 && ostrov[x + 1][y1] == oldColor)
            {
                if (!push(x + 1, y1)) return;
                spanRight = 1;
            }
            else if (spanRight && x < w - 1 && ostrov[x + 1][y1] != oldColor)
            {
                spanRight = 0;
            }
            if (!spanDDLeft && x > 0 && y1 + 1 < h && ostrov[x - 1][y1 + 1] == oldColor)  
            {
                if (!push(x - 1, y1 + 1)) return;
                spanDDLeft = 1;
            }
            else if (spanDDLeft && x > 0 && y1 + 1 < h && ostrov[x - 1][y1 + 1] != oldColor)
            {
                spanDDLeft = 0;
            }
            if (!spanDDRight && x + 1 < w && y1 + 1 < h && ostrov[x + 1][y1 + 1] == oldColor) 
            {
                if (!push(x + 1, y1 + 1)) return;
                spanDDRight = 1;
            }
            else if (spanDDRight && x + 1 < w && y1 + 1 < h && ostrov[x + 1][y1 + 1] != oldColor)
            {
                spanDDRight = 0;
            }
            if (!spanDULeft && x > 0 && y1 > 0 && ostrov[x - 1][y1 - 1] == oldColor)
            {
                if (!push(x - 1, y1 - 1)) return;
                spanDULeft = 1;
            }
            else if (spanDULeft && x > 0 && y1 > 0 && ostrov[x - 1][y1 - 1] != oldColor)
            {
                spanDULeft = 0;
            }
            if (!spanDURight && x + 1 < w && y1 > 0 && ostrov[x + 1][y1 - 1] == oldColor)
            {
                if (!push(x + 1, y1 - 1)) return;
                spanDURight = 1;
            }
            else if (spanDURight && x + 1 < w && y1 > 0 && ostrov[x + 1][y1 - 1] != oldColor)
            {
                spanDURight = 0;
            }
            y1++;
        }
    }
}


int main()
{

    cin >> h >> w;
    h += 2;
    w += 2;
    ostrov = new int*[w];
    for (int i = 0; i < w; i++) {
        ostrov[i] = new int[h];
        for (int j = 0; j < h; j++)
            ostrov[i][j] = 1;
    }
    int n;
    cin >> n;
    int color = 1; 
    int act = 0;  //actual color
    int prev = 0; //last color
    for (int i = 0; i < n; i++) {
        color++;
        suc = color % 2;
        prev = (color - 1) % 2;
        int x, y;
        cin >> x >> y;
        if (ostrov[y][x] == act) {
            cout << (w * h) - area << endl;
            color--;
            continue; 
        }
        area = 0;
        ostrov[y][x] = 5;
        floodFillScanlineStack(0, 0, act, prev); 
        cout << (w * h) - area << endl;
    }
}

EDIT: Now I realized that this enclosed area doesn't need to be rectangle or square, it could be polygon. Also, there could be more polygons in the map. And after I get a coordinates of part of the wall I must tell:
1.) if it make some polygon(enclosed area) - if yes, how big area which was't enclosed before I must add to are which was enclosed;
2.) if it doesn't make some polygon and it isn't in area which was already enclosed, add to enclosed area number 1(because this box of map water can't flood);
3.) if it is in area which was already enclosed, add nothing, because it doesn't enclose any more area.

Jozef Bugoš
  • 137
  • 1
  • 2
  • 8
  • Well, you didn't provide your code, so it's kind of impossible to help. Also this sound like it would fit more on https://codereview.stackexchange.com/ – Šimon Tóth Sep 14 '15 at 16:31
  • 1
    @Let_Me_Be except the #1 most important criteria for anything to be on-topic on [codereview.se], is that the *code to be reviewed must be included in the question*. – Mathieu Guindon Sep 14 '15 at 16:38
  • I do not think that this would fit more on codereview, because I want some idea to solve task faster than with Floodfill, not review of my code – Jozef Bugoš Sep 16 '15 at 12:48
  • @JozefBugoš Where did you find this question ? – fjardon Sep 20 '15 at 15:01
  • @fjardon - there is a website from my country (Slovakia), where are some tasks to help you to improve your skills, when you are beginner at programming. I have done a lot of these tasks, but now I found 3 tasks, which I can't do and this is one of them. – Jozef Bugoš Sep 20 '15 at 15:04
  • @JozefBugoš Can you provide a link ? There may be more info on the original website and I'd like to review the other testcases to better understand what complexity is expected. – fjardon Sep 20 '15 at 15:23
  • @fjardon - link is here : [this particular task](https://www.ksp.sk/ulohy/zadania/1042/); [archive of tasks](https://www.ksp.sk/ulohy/) but it is in Slovak language, so I don't know if it will be helpful for you. – Jozef Bugoš Sep 20 '15 at 15:56
  • @fjardon from this [archive](https://www.ksp.sk/ulohy/14/) I have done tasks from 1 to 5, and I've problem solving 6.,7. and 8. task – Jozef Bugoš Sep 20 '15 at 16:04
  • @JozefBugoš: Have you tried to find a way to update the result, instead of recomputing from scratch for every new wall segment? Probably a data structure other than just the grid will be useful, but I don't have any specific suggestions for that. For speeding up your existing code, don't emulate 2D arrays with an array of pointers. Make a single large array, and index it with `a[ h * max_w + w ]` or similar. Looking up a value in your `ostrov` global variable requires a load from memory to get the pointer for that row's storage array, then another load within the row. – Peter Cordes Sep 21 '15 at 02:48
  • Also, pass things as function arguments. Having many global data structures makes it harder to keep tracks of which functions look at / modify what. When you say your code is too slow, did you compile with optimization on? How slow is it? How fast are you hoping it could be? – Peter Cordes Sep 21 '15 at 02:51
  • Using a single array, instead of pointers to arrays, should be a big speedup. Then the addresses of the neighbouring elements are all easy to determine from the pair you pop off the stack (already compressed into x*h + y form!) For your current data structure, it'd be better to store pairs on the stack, rather than having to compress and decompress them with integer division. – Peter Cordes Sep 21 '15 at 03:11
  • @Peter Cordes I will try it, but I don't think it will be enough. There must be another way, probably FloodFill isn't good choice... It is too slow for 6th test entry and there 4 more with bigger size of the map. I tried to find a way to update the result, but it is impossible, at least i think so, because I just can't use FloodFill on some selected area, because then the map would have lot of different values in the array and it would be impossible to apply FloodFill again, 'cause it needs just one old color and one new color – Jozef Bugoš Sep 21 '15 at 10:35
  • (1) Where does the flood fill start from? (2) Is the boundary of the map a barrier, or does the water wrap around the edges? – Patrick87 Sep 21 '15 at 14:35
  • @Patrick87 I added one extra layer to every side of array, so flood fill starts now on coordination [0,0] and the boundary of map is a barrier, if I understood you correctly. – Jozef Bugoš Sep 21 '15 at 16:00
  • 1
    If the boundary is a barrier and the flood starts at (0, 0), couldn't you just wall off (0, 0) with three walls at (1, 0), (0, 1) and (1, 1) and prevent the water from flooding the remaining O(w*h) of the map? If you have only two or fewer walls to place, you can't prevent any non-wall areas from being flooded. – Patrick87 Sep 21 '15 at 16:40
  • @Patrick87 You don't understand the task. At the beginning the map is empty. But I wiil get position of part of wall and after placing this part to the map I must print number of area which water can't flood. Also, Flood start from every box on edges, so at (0,0), (0,1) (0,...w); (1,0), (1, ...h); etc – Jozef Bugoš Sep 21 '15 at 17:04
  • I see. Well then, are you looking for proof of the following logical argument? Only a closed path of walls will enclose more area than the walls themselves occupy; the optimal closed path will be convex, since any concave path could be changed to a convex one that encloses more area; when choosing points on a discrete lattice, "convex" is going to imply a rectangle of some kind; and of all rectangles, the one enclosing the most area will be the longest, most square, shape that fits on the map. That last condition leaves some room to search but searching that is vastly more efficient than – Patrick87 Sep 21 '15 at 17:35
  • @Patrick87 I am not sure if this is it. Now I realized that this enclosed area doesn't need to be rectangle or square, it could be polygon. Also, there could be more polygons in the map. And after I get a coordinates of part of the wall I must tell: 1.) if it make some polygon(enclosed area) - if yes, how big area which was't enclosed before I must add to are which was enclosed; 2.) if it doesn't make some polygon and it isn't in area which was already enclosed, add to enclosed area number 1(because this box of map water can't flood); 3.) if it is in area which was already enclosed, add – Jozef Bugoš Sep 21 '15 at 18:18
  • nothing, because it doesn't enclose any more area; I will edit my question with explanation of input which I gave as example. – Jozef Bugoš Sep 21 '15 at 18:21
  • Based on your edit and a closer reading of the question - it sounds like you're not placing the walls at all. The input file describes where the walls are, and your only job is to say for each line how many squares aren't flooded. If that's right, I have an idea I'll put in an answer. – Patrick87 Sep 21 '15 at 18:25
  • @Patrick87 Yes, I'm not placing the walls, they are part of input. You are completely right. I'm sorry if it wasn't clear enough, it was just my translation of the task and English isn't my native language. If you have an idea I will be very grateful. – Jozef Bugoš Sep 21 '15 at 18:31

1 Answers1

0

Here's a thought that might help. The only way to enclose more space than is occupied by the walls themselves is to create a closed path of some kind. Consider what performing a 4-way flood fill starting at the point a new wall segment is placed would accomplish. It would (with minor modification) allow you to detect a closed path - and alert you to the fact that there are non-wall spaces which are safe from flooding.

Really, this will probably be better to think about as a sort of depth-first search where you're looking for the original point to appear a second time. You'll want to continue the search as one new wall segment may complete a number of closed paths (actually, this is probably not true; to be a barrier against 8-way floodfill, the last required piece of wall would only belong to one new loop formed (unless you were placing it inside some other shape already formed).

Once you have detected a closed loop, you need only fill in the interior squares with some other color (if white is blank and black is walls, maybe red or something); any future walls on red squares don't help. Figuring out how to fill the interior is now the only problem - and it's an easy one at that. Simply check squares all around the new wall. Once you find a square, scan either horizontally or vertically (depending on your direction from point) and see if you cross over the path again. If you cross through an odd number of times, you're inside the shape; if an even number of times, you're outside. There will be some blank space(s) around the new wall piece that completes a closed path, since otherwise, any loop must already have been closed.

Phew. I think that should be significantly faster than flood-filling at each iteration. Still not a walk in the park, but some food for thought.

Patrick87
  • 27,682
  • 3
  • 38
  • 73
  • Thanks! I will look at it and try it. I'll notice you then. – Jozef Bugoš Sep 21 '15 at 19:25
  • @Patrik87 I was thinking about your idea and my conclusion is that original DFS which detect cycle won't work for one reason, it detect only one cycle, while there may be longer cycle - and I need the longest cycle. If I find it, I will check where is the closed area and fill it with my FloodFill algorithm,but I need the longest cycle. I've found this [article](http://stackoverflow.com/questions/12367801/finding-all-cycles-in-undirected-graphs), but I don't understand code given by Alex Kemper. Could you explain it to me, or rewrite it to C++? Also I need to check the cycle only for one vertex – Jozef Bugoš Sep 24 '15 at 18:12