Dark streets

Zadání
Viz odkaz: Neosvětlené ulice.

Schéma řešení
1. Průchod do šiřky (BFS)
Podle zadání rozumíme vzdáleností dvou křižovatek nejmenší možný počet neosvětlených (černých) hran na cestě mezi nimi. Do obyčejné fronty (označené jako černá fronta) si tedy budeme ukládat, jako vždy v BFS, postupně uzly ve vzdálenosti 0, 1, 2, atd. od výchozí křižovatky. Kdykoli odebereme křižovatku X z čela černé fronty, uložíme na konec černé fronty všechny křižovatky Y, které sousedí s X a ulice (X Y) je neosvětlená (černá). Pokud některá Y sousedí s další křižovatkou Z a přitom ulice (Y Z) je osvětlená (bílá), vložíme na konec černé fronty také všechny křižovatky W, do kterých se lze dostat z Y pouze použitím osvětlených (bílých) ulic a ze kterých vede černá ulice. Všechny tyto křižovatky W mají totiž od X jednotkovou vzdálenost, tudíž i také mají od startovní křižovatky stejnou vzdálenost, jako má Y od startovní křižovatky, a patří tedy na aktuální konec černé fronty. Nalezení všech potřebných křižovatek W provedeme opět pomocí standardního BFS, musíme mít jen jinou frontu, v kódu je označena jako bílá.
Při navštěvování a probírání dalších křižovatek, uvažujeme vždy jen křižovatky dosud nenavštívené (které dosud nebyly ani v černé ani v bílé frontě), jak je to ostatně v BVS zcela obvyklé. Pokud startovní křižovatka sousedí s osvětlenou ulicí, prohledáme nejprve osvětlenou oblast, v níž se nachází, s pomocí bílé fronty.
Jakmile při práci v kterékoli frontě narazíme na cílovou křižovatku, končíme.
Ulice jsou uloženy ve 3D poli, první dvě dimenze určují souřadnice křižovatky, ve třetí dimenzi jsou uloženy čtyří logické hodnoty, určující, zda vede osvětlená nebo neosvětlená ulice z této křižovatky na východ, sever, západ, jih. Každá ulice je tak reprezentována dvakrát, v obou svých krajních křižovatkách. Lze uvažovat i o jiných a úspornějších reprezentacích, tato byla zvolena pro svou relativní intuitivní přehlednost.

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.StringTokenizer;
 
public class Example{
 
static int M, N, L;
static int EAST=0, NORTH=1, WEST=2, SOUTH=3;  // indices of directions
static int [] dx = {+1, 0, -1, 0}; // neighbours offsets in the grid
static int [] dy = {0, +1, 0, -1}; // dtto
 
static boolean [][][] node;  // node = street crossing
static int [] queueBlack, queueWhite;   // queues for nodes
static boolean [][] used;
 
static int headBlack, tailBlack, headWhite , tailWhite ;
static int startX, startY, targetX, targetY;
static int currdist, inextdist; // register distances of nodes in the queue
 
 
static boolean hasBlackEdge(int y, int x) {
  if ((x < N-1) && (!node[y][x][EAST])) return true;
  if ((x >  0 ) && (!node[y][x][WEST])) return true;
  if ((y < M-1) && (!node[y][x][NORTH])) return true;
  if ((y >  0 ) && (!node[y][x][SOUTH])) return true;
  return false;
}
 
static boolean  wasUsed (int y, int x) {
  if ((x >= N) || (x < 0) || (y >= M ) || (y < 0)  || used[y][x] ) return true;
  used[y][x] = true;
  return false;
}
 
static boolean searchWhites(int y, int x) {
  int neighX, neighY;
  tailWhite = headWhite = 0;
  queueWhite[headWhite] = y*N+x;    // unique node label  !!!
  while (headWhite <= tailWhite) {
    y = queueWhite[headWhite]/N; x = queueWhite[headWhite++]%N;
    for(int dir = 0; dir < 4; dir++)
      if (node[y][x][dir]) {
        neighY = y+dy[dir]; neighX= x + dx[dir];
        if (wasUsed(neighY, neighX)) continue;
        if ((neighY == targetY) && (neighX == targetX)) 
          { System.out.printf("%d\n", currdist);  return true; } // found
        if (hasBlackEdge(neighY, neighX))
          queueBlack[++tailBlack] = neighY*N + neighX; // push to black
        queueWhite[++tailWhite] = neighY*N + neighX;   // push white anaway
    }
  } // while unempty white q
  return false;
}
 
static void searchBlacks(){
   int y, x, neighX, neighY;
   while(headBlack<= tailBlack) {
     if (headBlack== inextdist) {currdist++; inextdist = tailBlack+1;}     
     y = queueBlack[headBlack]/N; x = queueBlack[headBlack++]%N;
     for(int dir = 0; dir < 4; dir++) {
       neighY = y+dy[dir]; neighX = x + dx[dir];
       if ( wasUsed(neighY, neighX)) continue;
       if ((neighY == targetY) && (neighX == targetX)) 
          { System.out.printf("%d\n", currdist); return; }
       queueBlack[++tailBlack] = neighY*N + neighX;   // push this node anyway
       if (searchWhites(neighY, neighX)) return;   // push all white-adjacent nodes
    } // for 4 directions
  } // while unempty q
}
 
 
// ***************************************************************************
//                            M A I N
// ***************************************************************************
  public static void main(String[] args) throws IOException {
   getvals();
   used[startX][startY] = true;
   currdist = head1 = tail1  = 0;
   queueBlack[tail1] = startX*N + startY;     //unique node label  !!!
   if (!searchWhites(startX, startY)) { // if not in the same illuminated region
     inextdist = tail1+1; currdist = 1;
     searchBlacks();
   }
  }
 
// ***************************************************************************
//                            I / O
// ***************************************************************************
  static void  getvals()  throws IOException {
  BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
  StringTokenizer st = new StringTokenizer(br.readLine());
  t1 = System.currentTimeMillis();
  N = Integer.valueOf(st.nextToken());
  M = Integer.valueOf(st.nextToken());
  L = Integer.valueOf(st.nextToken());
  node = new boolean [M][N][4];
  used = new boolean[M][N];
  queueBlack = new int[M*N];
  queueWhite = new int[M*N];
 
  st = new StringTokenizer(br.readLine());
  startY = Integer.valueOf(st.nextToken());
  startX = Integer.valueOf(st.nextToken());
  targetX = Integer.valueOf(st.nextToken());
  targetY = Integer.valueOf(st.nextToken());
 
  int x1, x2, y1, y2;
  for(int i = 1; i <= L; i++) {
    st = new StringTokenizer(br.readLine());
    x1 = Integer.valueOf(st.nextToken());
    y1 = Integer.valueOf(st.nextToken());
    x2 = Integer.valueOf(st.nextToken());
    y2 = Integer.valueOf(st.nextToken());
    for(int y = y1; y <= y2; y++)
      for(int x = x1; x < x2; x++)
          node[y][x][EAST] = node[y][x+1][WEST] = true;
    for(int y = y1; y < y2; y++)
      for(int x = x1; x <= x2; x++)
          node[y][x][NORTH] = node[y+1][x][SOUTH] = true;
  }
}
}

2. Dijkstrův algoritmus
Při použití Dijkstrova algoritmu postupujeme standardně, vybudujeme graf, můžeme použít stejnou reprezentaci grafu, jakou bychom použili v předcházející variantě s procházením do šířky. Osvětlené ulice ohodnotíme například nulou, neosvětlené jedničkou a Dijkstrův algoritmus už za nás sám hledá nejkratší cestu (nebo alespoň její délku, tedy počet černých hran, tedy počet jedniček na cestě) ze startu do cíle. Tuto variantu nelze asi příliš dále komentovat, po ohodnocení uvedeném výše se úloha redukuje na zcela mechanickou základní aplikaci Dijkstrova algoritmu, bez žádných dodatečných nápadů nebo modifikací.

Bez úpravy uvádíme níže úspěšné studentské řešení vzniklé během cca 2 hodin během zkoušky. Centrální pro celé řešení je funkce solve().

import java.util.Comparator;
 
public class SearchNodeComparator implements Comparator<SearchNode>{
    @Override
    public int compare(SearchNode o1, SearchNode o2) {
        return o1.price - o2.price;
    }
}
// **************************************************************
public class SearchNode {
    public final int x;
    public final int y;
    public final int price;
 
    public SearchNode(int x, int y, int price) {
        this.x = x;
        this.y = y;
        this.price = price;
    }
}
// **************************************************************
 
import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.util.PriorityQueue;
 
public class Main {
 
    private static InputStream stream;
    private static int N;
    private static int M;
    private static int L;
    private static int[][] cross;
    private static boolean[][] right;
    private static boolean[][] up;
    private static int sx, sy, ex, ey; //start and end coordinates
    private static PriorityQueue<SearchNode> queue;
 
    public static void main(String[] args) throws IOException {
        init();
        setStream(false);
        getInput();
        //testInput();
        solve();
    }
 
    private static void setStream(boolean file) throws FileNotFoundException {
        if (file) {
            File publicData = new File("datapub/pub01.in");
            if (!publicData.exists()) {
                System.out.println("CHYBA public data neexistuji");
                System.exit(1);
            }
            stream = new BufferedInputStream(new FileInputStream(publicData));
        } else {
            stream = System.in;
        }
    }
 
    private static int readInt() throws IOException {
        int sum = 0;
        int scan = stream.read();
        while (scan < 48 || scan > 57) {
            scan = stream.read();
        }
        while (scan >= 48 && scan <= 57) {
            sum *= 10;
            sum += scan - 48;
            scan = stream.read();
        }
        return sum;
    }
 
    private static void getInput() throws IOException {
        N = readInt();
        M = readInt();
        L = readInt();
        sx = readInt();
        sy = readInt();
        ex = readInt();
        ey = readInt();
        cross = new int[N][M];
        for (int i = 0; i < N; i++) {
            for (int j = 0; j < M; j++) {
                cross[i][j] = Integer.MAX_VALUE;
            }
        }
        right = new boolean[N][M];
        up = new boolean[N][M];
        for (int i = 0; i < L; i++) {
            int a, b, c, d;
            a = readInt();
            b = readInt();
            c = readInt();
            d = readInt();
            lightStreets(a, b, c, d);
 
        }
    }
 
    private static void testInput() {
        System.out.println("N " + N + " M " + M + " L " + L);
        for (int y = M-1; y >=0; y--) {
            for (int x = 0; x < N; x++) {
                System.out.print(up[x][y]?"X ":"O ");
            }
            System.out.println("");
        }
        System.out.println("");
        for (int y = M-1; y >=0; y--) {
            for (int x = 0; x < N; x++) {
                System.out.print(right[x][y]?"X ":"O ");
            }
            System.out.println("");
        }
    }
 
    private static void solve() {
        cross[sx][sy] = 0;
        SearchNode node = new SearchNode(sx, sy, 0);
        while (node.x != ex || node.y != ey) {
            expand(node);
            node = queue.poll();
        }
        System.out.println(node.price);
    }
 
    private static void init() {
        queue = new PriorityQueue<>(1024, new SearchNodeComparator());
    }
 
    private static void expand(SearchNode node) {
        int x = node.x; //x sloupec 
        int y = node.y; //y je radek
        int p = node.price;
        if (up[x][y]) {
            if (y < M-1 && p < cross[x][y + 1]) {
                cross[x][y + 1] = p;
                queue.add(new SearchNode(x, y + 1, p));
            }
        } else {
            if (y < M-1 && (p + 1) < cross[x][y + 1]) {
                cross[x][y + 1] = p + 1;
                queue.add(new SearchNode(x, y + 1, p + 1));
            }
        }
        if (y > 0) {
            if (up[x][y - 1]) {
                if (p < cross[x][y - 1]) {
                    cross[x][y - 1] = p;
                    queue.add(new SearchNode(x, y - 1, p));
                }
            } else {
                if ((p + 1) < cross[x][y - 1]) {
                    cross[x][y - 1] = p + 1;
                    queue.add(new SearchNode(x, y - 1, p + 1));
                }
            }
        }
        if (right[x][y]) {
            if (x < N-1 && p < cross[x + 1][y]) {
                cross[x + 1][y] = p;
                queue.add(new SearchNode(x + 1, y, p));
            }
        } else {
            if (x < N-1 && (p + 1) < cross[x + 1][y]) {
                cross[x + 1][y] = p + 1;
                queue.add(new SearchNode(x + 1, y, p + 1));
            }
        }
        if (x > 0) {
            if (right[x - 1][y]) {
                if (p < cross[x - 1][y]) {
                    cross[x - 1][y] = p;
                    queue.add(new SearchNode(x - 1, y, p));
                }
            } else {
                if ((p + 1) < cross[x - 1][y]) {
                    cross[x - 1][y] = p + 1;
                    queue.add(new SearchNode(x - 1, y, p + 1));
                }
            }
        }
    }
 
    private static void lightStreets(int a, int b, int c, int d) {
        for (int x = a; x <= c; x++) {
            for (int y = b; y < d; y++) {
                up[x][y] = true;
            }
        }
        for (int x = a; x < c; x++) {
            for (int y = b; y <= d; y++) {
                right[x][y] = true;
            }
        }
    }
}

courses/a4b33alg/komentare/darkstreets.txt · Last modified: 2018/02/21 19:33 (external edit)