// 20/03/18 22:40

import java.io.*;
import java.util.*;

class Main{
    final static long INF = Long.MAX_VALUE / 2;
    final static int MOD = 1_000_000_007;
    final static int SIZE = 1_000_000;
    long[] fac = new long[SIZE];
    long[] inv = new long[SIZE];
    long[] finv = new long[SIZE];
    FastScanner sc = new FastScanner();

    public static void main(String[] args) {
        new Main().solve();
    }

    void solve(){

        String s = sc.next();
        long sum = 0;
        int d = 0;
        boolean flag = false;

        for(int i = 0; i < s.length(); i++){
            if(s.charAt(i) == 'A'){
                d++;
                if(i + 2 < s.length() && s.charAt(i + 1) == 'B' && s.charAt(i + 2) == 'C'){
                    sum += d;
                    flag = true;
                    i += 2;
                }
            }else if(flag && s.charAt(i) == 'B' && i + 1 < s.length() && s.charAt(i + 1) == 'C'){
                sum += d;
                i++;
            }else{
                d = 0;
                flag = false;
            }
        }

        System.out.println(sum);

    }

    long gcd(long a, long b){ // return aとbの最大公約数
        if(b == 0){
          return a;
        }
        return gcd(b, a % b);
    }

    long lcm(long a, long b){ // return aとbの最小公倍数
        return a * b / gcd(a, b);
    }

    long inv(long a){ // return aの逆元 (mod MOD)
        return pow(a, MOD - 2);
    }

    long pow(long a, long r){ // return a^r (mod MOD)
        long sum = 1;
        while(r > 0){
            if((r & 1) == 1){ // 2進数表記で末尾1の時
                sum *= a;
                sum %= MOD;
            }
            a *= a;
            a %= MOD;
            r >>= 1;
        }
        return sum;
    }

    long modFact(long n){ // retur n! (mod MOD)
        if(n == 0){
            return 1;
        }
        return n * modFact(n - 1) % MOD;
    }

    long fact(long n){ // return n!
        if(n == 0){
            return 1;
        }
        return n * fact(n - 1);
    }

    void initCOMB(){
        fac[0] = fac[1] = 1;
        inv[1] = 1;
        finv[0] = finv[1] = 1;
        for(int i = 2; i < SIZE; i++){
            fac[i] = fac[i - 1] * i % MOD;
            inv[i] = MOD - inv[MOD % i] * (MOD / i) % MOD;
            finv[i] = finv[i - 1] * inv[i] % MOD;
        }
    }

    long modComb(int n, int r){ // return nCr (先にinitCOMB()必要)
        if(n < r || n < 0 || r < 0) return 0;
        return fac[n] * finv[r] % MOD * finv[n - r] % MOD;
    }

    long comb(long n, long r){ // return nCr
        long num = 1;
        for(long i = 1; i <= r; i++){
            num = num * (n - i + 1) / i;
        }
        return num;
    }

    boolean isPrime(long a){ // aの素数判定
        if(a <= 1) return false;
        for(int i = 2; i * i <= a; i++){
            if(a % i == 0) return false;
        }
        return true;
    }

    String nextPermutation(String s){ // return sの次の順列
        ArrayList<Character> list = new ArrayList<>();
        for(int i = 0; i < s.length(); i++) list.add(s.charAt(i));

        int pivotPos = -1;
        char pivot = 0;
        for(int i = list.size() - 2; i >= 0; i--){
            if(list.get(i) < list.get(i+1)){
    			pivotPos = i;
    			pivot = list.get(i);
    			break;
    		}
    	}

        if(pivotPos == -1 && pivot == 0) return null;

        int L = pivotPos + 1;
        int R = list.size() - 1;
    	int minPos = -1;
    	char min = Character.MAX_VALUE;
    	for(int i = R; i >= L; i--){
    		if(pivot < list.get(i)){
    			if(list.get(i) < min){
    				min = list.get(i);
    				minPos = i;
    			}
    		}
    	}

    	Collections.swap(list, pivotPos, minPos);
    	Collections.sort(list.subList(L, R + 1));

    	StringBuilder sb = new StringBuilder();
    	for(int i=0; i<list.size(); i++) sb.append(list.get(i));

    	return sb.toString();
    }

    boolean nextPermutation(long[] a){
        for(int i = a.length - 1; i > 0; i--){
            if(a[i - 1] < a[i]){
                int swapIndex = find(a[i - 1], a, i, a.length - 1);
                long temp = a[swapIndex];
                a[swapIndex] = a[i - 1];
                a[i - 1] = temp;
                Arrays.sort(a, i, a.length);
                return true;
            }
        }
        return false;
    }

    int find(long dest, long[] a, int s, int e){
        if(s == e){
            return s;
        }
        int m = (s + e + 1) / 2;
        return a[m] <= dest ? find(dest, a, s, m - 1) : find(dest, a, m, e);
    }

    void elimination(int[][] a, int[] b) {
        int n = a.length;
        double f;
        for(int k = 0; k < n - 1; k++){
            for(int i = k + 1; i < n; i++){
                f = - a[i][k] / a[k][k];
                for(int j = k + 1; j < n; j++){
                    a[i][j] += f * a[k][j];
                }
                b[i] += f * b[k];
            }
            for(int i = n - 1; i >= 0; i--){
                for(int j = i + 1; j < n; j++){
                    b[i] -= a[i][j] * b[j];
                }
                b[i] = b[i] / a[i][i];
            }
        }
   }

}

class Pair implements Comparable<Pair>{
    long a, b;
    public Pair(long i, long j){
        a = i;
        b = j;
    }

    @Override
    public int compareTo(Pair p){
        if(this.b < p.b) return -1;
        else if(this.b > p.b) return 1;
        else return 0;
    }
}

class FastScanner {
    private final InputStream in = System.in;
    private final byte[] buffer = new byte[1024];
    private int ptr = 0;
    private int buflen = 0;
    private boolean hasNextByte(){
        if(ptr < buflen){
            return true;
        }else{
            ptr = 0;
            try{
                buflen = in.read(buffer);
            }catch(IOException e){
                e.printStackTrace();
            }
            if(buflen <= 0){
                return false;
            }
        }
        return true;
    }
    private int readByte(){
        if(hasNextByte()) return buffer[ptr++];
        else return -1;
    }
    private static boolean isPrintableChar(int c){
        return 33 <= c && c <= 126;
    }
    public boolean hasNext(){
        while(hasNextByte() && !isPrintableChar(buffer[ptr])) ptr++;
        return hasNextByte();
    }
    public String next(){
        if(!hasNext()) throw new NoSuchElementException();
        StringBuilder sb = new StringBuilder();
        int b = readByte();
        while(isPrintableChar(b)){
            sb.appendCodePoint(b);
            b = readByte();
        }
        return sb.toString();
    }
    public long nextLong(){
        if(!hasNext()) throw new NoSuchElementException();
        long n = 0;
        boolean minus = false;
        int b = readByte();
        if(b == '-'){
            minus = true;
            b = readByte();
        }
        if(b < '0' || '9' < b){
            throw new NumberFormatException();
        }
        while(true){
            if('0' <= b && b <= '9'){
                n *= 10;
                n += b - '0';
            }else if(b == -1 || !isPrintableChar(b)){
                return minus ? -n : n;
            }else{
                throw new NumberFormatException();
            }
            b = readByte();
        }
    }
    public int nextInt(){
        long nl = nextLong();
        if (nl < Integer.MIN_VALUE || nl > Integer.MAX_VALUE) throw new NumberFormatException();
        return (int) nl;
    }
    public double nextDouble(){
        return Double.parseDouble(next());
    }
}


import java.util.Scanner;

public class Main {

	public static void main(String[] args) {
		try (Scanner sc = new Scanner(System.in)) {

			char[] str = sc.next().replaceAll("BC", "D").toCharArray();
			int count = 0;
			int acount = 0;
			for (char ch : str) {
				switch(ch) {
				case 'A':
					acount++;
					break;
				case 'D':
					count += acount;
					break;
				default:
					acount = 0;
				}
			}

			System.out.println(count);
		}
	}
}

Global OOD problem IDs are disjoint from train, validation, and in-distribution test problem IDs.

Score : 600 points

Problem Statement

You are given a string s consisting of A, B and C.

Snuke wants to perform the following operation on s as many times as possible:

Choose a contiguous substring of s that reads ABC and replace it with BCA.

Find the maximum possible number of operations.

Constraints

1 \leq |s| \leq 200000

Each character of s is A, B and C.

Input

Input is given from Standard Input in the following format:

s

Output

Find the maximum possible number of operations.

Sample Input 1

ABCABC

Sample Output 1

3

You can perform the operations three times as follows: ABCABC → BCAABC → BCABCA → BCBCAA. This is the maximum result.

Sample Input 2

C

Sample Output 2

0

Sample Input 3

ABCACCBABCBCAABCB

Sample Output 3

6

Submitted source-code file size, in bytes.

Execution CPU time reported by official Project CodeNet metadata, in milliseconds.

Peak memory usage reported by official Project CodeNet metadata, in kilobytes.


import java.util.*;

class Main {
	public static void main(String[] args) {
		Scanner sc = new Scanner(System.in);
		String S = sc.next();
		int cnt=0;
		for (int i=0;i<S.length()-2;i++) {
			int right=0;
			int left=0;
			if (S.charAt(i)=='A' && S.charAt(i+1)=='B' && S.charAt(i+2)=='C') {
				cnt++;
				int j=i-1;
				left=j+1;
				if (left==right) {
					cnt++;
				}
				while (j>0 && S.charAt(j)=='A'/*S.charAt(j)=='C' && S.charAt(j-1)=='B'*/) {
					cnt++;
					j--;
				}
				int k=i+3;
				right=k;
				while (k<S.length()-1 && S.charAt(k)=='B' && S.charAt(k+1)=='C'/*S.charAt(k)=='A'*/) {
					cnt++;
					k++;
				}
			}
		}
		System.out.println(cnt);
	}
}


import java.io.InputStream;
import java.io.PrintStream;
import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.IntStream;

public class Main {

  public static void main(String[] args) {
    solve(System.in, System.out);
  }

  static void solve(InputStream is, PrintStream os) {
    Scanner sc = new Scanner(is);

    /* read */
    String s = sc.next();

    os.println(search(s));

  }

//  private static int search(String s) {
//
//    int n = s.length();
//
//    int sum = 0;
//    int j = 0;
//    while (0 <= j && j < n) {
//      // for each, find A
//      if (s.charAt(j) != 'A') {
//        j = s.indexOf("A", j);
//        continue;
//      }
//      // found A
//
//      int bcCount = 0;
//      int i = j + 1;
//      while (i < n - 1) {
//        if (s.charAt(i) == 'A') {
//          i++;
//        } else if (s.charAt(i) == 'B' && s.charAt(i + 1) == 'C') {
//          bcCount++;
//          i += 2;
//        } else {
//          break;
//        }
//      }
//      sum += bcCount;
//      j++;
//    }
//    return sum;
//  }

  private static int search(String s) {

    int n = s.length();
    char[] c = s.toCharArray();

    int sum = 0;
    int j = 0;
    while (0 <= j && j < n) {

      // for each, find A
      // found A
      if (c[j] != 'A') {
        j = s.indexOf("A", j + 1);
        continue;
      }

      int aCount = 0;
      int bcCount = 0;
      int i = j;
      while (i < n - 1) {
        if (c[i] == 'A') {
          aCount++;
          i++;
        } else if (c[i] == 'B' && c[i + 1] == 'C') {
          bcCount++;
          i += 2;
          sum += aCount;
        } else {
          break;
        }
      }
      j = i;
    }
    return sum;
  }

//  private static int search(String s) {
//
//    int n = s.length();
//
//    int sum = 0;
//    for (int j = 0; j < n; j++) {
//
//      // for each, find A
//      // found A
//      if (s.charAt(j) != 'A') continue;
//
//      int bcCount = 0;
//      int i = j + 1;
//      while (i < n - 1) {
//        if (s.charAt(i) == 'A') {
//          i++;
//        } else if (s.charAt(i) == 'B' && s.charAt(i + 1) == 'C') {
//          bcCount++;
//          i += 2;
//        } else {
//          break;
//        }
//      }
//      sum += bcCount;
//    }
//    return sum;
//  }
}


/**
 * Created at 20:51 on 2019-06-02
 */

import java.io.*;
import java.util.*;
import java.math.BigInteger;

public class Main {

  static FastScanner sc = new FastScanner();

  public static void main(String[] args) {

    String s = sc.next();
    StringBuffer sb = new StringBuffer(s);
    sb = sb.reverse();
    s = sb.toString();

    s = s.replaceAll("CB", "X");

    long count = 0;
    long countX = 0;
    for (int i=0; i<s.length(); i++) {
      char c = s.charAt(i);
      if (c == 'X') {
        countX++;
      } else if (c == 'A') {
        count += countX;
      } else if (c == 'B' || c == 'C') {
        countX = 0;
      }
    }

    System.out.println(count);

  }

  static class Debugger {

    static long startTime;
    static boolean on = true;

    //入力受け取り後に呼び出す(入力時間をカウントしないため)
    static void start() {
      startTime = System.currentTimeMillis();
    }

    static void printTime() {
      if (on) System.out.println("(" + (System.currentTimeMillis() - startTime) + "ms)");
    }

    static void toggle() {
      on = !on;
    }

  }

  static class BigInt extends BigInteger {

    BigInteger MOD = new BigInteger("1000000007");
    static BigInt ZERO = new BigInt(0);
    static BigInt ONE = new BigInt(1);
    static BigInt TEN = new BigInt(10);

    public BigInt(long l) {
      super(Long.toString(l));
    }

    public BigInt(BigInteger i) {
      super(i.toByteArray());
    }

    public BigInt pow(int i) {
      return new BigInt(super.pow(i));
    }

    public BigInt[] divideAndRemainder(long l) {
      return divideAndRemainder(new BigInt(l));
    }

    public BigInt[] divideAndRemainder(BigInt i) {
      BigInt[] ret = new BigInt[2];
      BigInteger[] dAndR = super.divideAndRemainder(i);
      ret[0] = new BigInt(dAndR[0]);
      ret[1] = new BigInt(dAndR[1]);
      return ret;
    }

    public BigInt mod(long l) {
      return mod(new BigInt(l));
    }

    public BigInt mod(BigInt i) {
      return new BigInt(super.mod(i));
    }

    public BigInt mod() {
      return new BigInt(super.mod(MOD));
    }

    public BigInt add(long l) {
      return add(new BigInt(l));
    }

    public BigInt add(BigInt i) {
      return new BigInt(super.add(i));
    }

    public BigInt subtract(long l) {
      return subtract(new BigInt(l));
    }

    public BigInt subtract(BigInt i) {
      return new BigInt(super.subtract(i));
    }

    public BigInt multiply(long l) {
      return multiply(new BigInt(l));
    }

    public BigInt multiply(BigInt i) {
      return new BigInt(super.multiply(i));
    }

    public BigInt divide(long l) {
      return divide(new BigInt(l));
    }

    public BigInt divide(BigInt i) {
      return new BigInt(super.divide(i));
    }

    public BigInt abs() {
      return new BigInt(super.abs());
    }

  }

  /*
    add()でインデックスを指定しない場合指定されたソート順に違わない位置に追加する
    (ただしソートされていることが前提となる)
    Comparatorが0を返したとき、それらの順序は保証しない
    (TreeSet, TreeMapと違い削除はしない)
   */
  static class TreeList<E> extends ArrayList<E> {

    Comparator<? super E> comparator;

    TreeList(Comparator<? super E> c) {
      super();
      comparator = c;
    }

    /*
      ソート済みのリストに要素を追加する
     */
    public boolean add(E e) {
      int lowIndex = 0;
      int highIndex = size() - 1;
      int index = 0;

      if (size() == 0) {
        super.add(e);
        return true;
      }

      if (comparator.compare(e, get(0)) < 0) {
        index = 0;
      } else if (comparator.compare(e, get(highIndex)) > 0) {
        index = highIndex + 1;
      } else {
        while (lowIndex <= highIndex) {

          if (highIndex == lowIndex + 1 || highIndex == lowIndex) {
            index = highIndex;
            break;
          }

          int midIndex = (lowIndex + highIndex) / 2;
          ;

          if (comparator.compare(e, get(midIndex)) > 0) {
            lowIndex = midIndex;
          } else {
            highIndex = midIndex;
          }

        }
      }

      super.add(index, e);
      return true;
    }

  }

  static class FastScanner {
    private final InputStream in = System.in;
    private final byte[] buffer = new byte[1024];
    private int ptr = 0;
    private int buflen = 0;

    private boolean hasNextByte() {
      if (ptr < buflen) {
        return true;
      } else {
        ptr = 0;
        try {
          buflen = in.read(buffer);
        } catch (IOException e) {
          e.printStackTrace();
        }
        if (buflen <= 0) {
          return false;
        }
      }
      return true;
    }

    private int readByte() {
      if (hasNextByte()) return buffer[ptr++];
      else return -1;
    }

    private static boolean isPrintableChar(int c) {
      return 33 <= c && c <= 126;
    }

    private void skipUnprintable() {
      while (hasNextByte() && !isPrintableChar(buffer[ptr])) ptr++;
    }

    public boolean hasNext() {
      skipUnprintable();
      return hasNextByte();
    }

    public String next() {
      if (!hasNext()) throw new NoSuchElementException();
      StringBuilder sb = new StringBuilder();
      int b = readByte();
      while (isPrintableChar(b)) {
        sb.appendCodePoint(b);
        b = readByte();
      }
      return sb.toString();
    }

    public long nextLong() {
      if (!hasNext()) throw new NoSuchElementException();
      long n = 0;
      boolean minus = false;
      int b = readByte();
      if (b == '-') {
        minus = true;
        b = readByte();
      }
      if (b < '0' || '9' < b) {
        throw new NumberFormatException();
      }
      while (true) {
        if ('0' <= b && b <= '9') {
          n *= 10;
          n += b - '0';
        } else if (b == -1 || !isPrintableChar(b)) {
          return minus ? -n : n;
        } else {
          throw new NumberFormatException();
        }
        b = readByte();
      }
    }

    public int nextInt() {
      return (int) nextLong();
    }
  }

}


import java.util.*;
import java.lang.String;

public class Main {
    static class Position {
        public Position(int x, int y) {
            this.x = x; this.y = y;
        }
        public int x;
        public int y;
    }

    static int solve(String[] G) {
        int H = G.length;
        int W = G[0].length();
        int INF = Integer.MAX_VALUE/2;
        int[][] step = new int[H][W];
        ArrayList<Position> starts = new ArrayList<Position>();
        for (int i=0; i<H; i++) {
            for(int j=0; j<W; j++) {
                step[i][j] = INF;
                if (G[i].charAt(j) == '#') {
                    starts.add(new Position(i, j));
                }
            }
        }

        int[] dx = new int[] {0,1,0,-1};
        int[] dy = new int[] {1,0,-1,0};

        ArrayDeque<Position> que = new ArrayDeque<Position>();

        for (int i = 0; i < starts.size(); i++) {
            que.addLast(starts.get(i));
            step[starts.get(i).x][starts.get(i).y] = 0;
        }

        int res = 0;
        while (!que.isEmpty()) {
            Position pos = que.removeFirst();
            int x = pos.x; int y = pos.y;
            for (int i=0; i<dx.length; i++) {
                int nx = x + dx[i]; int ny = y + dy[i];
                if (nx<0 || H<=nx || ny<0 || W<=ny) {
                    continue;
                }

                if (step[nx][ny] > step[x][y] + 1) {
                    step[nx][ny] = step[x][y] + 1;
                    que.addLast(new Position(nx, ny));
                    res = Math.max(res, step[nx][ny]);
                }
            }
        }

        return res;
    }

    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        int H = sc.nextInt();
        int W = sc.nextInt();

        String[] A = new String[H];
        for (int i=0; i<H; i++) {
            A[i] = sc.next();
        }

        int ans = solve(A);

        System.out.println(ans);
    }
}

Score : 300 points

Problem Statement

You are given a grid of squares with H horizontal rows and W vertical columns, where each square is painted white or black.
HW characters from A_{11} to A_{HW} represent the colors of the squares.
A_{ij} is # if the square at the i-th row from the top and the j-th column from the left is black, and A_{ij} is . if that square is white.

We will repeatedly perform the following operation until all the squares are black:

Every white square that shares a side with a black square, becomes black.

Find the number of operations that will be performed.
The initial grid has at least one black square.

Constraints

1 \leq H,W \leq 1000

A_{ij} is # or ..

The given grid has at least one black square.

Input

Input is given from Standard Input in the following format:

H W
A_{11}A_{12}...A_{1W}
:
A_{H1}A_{H2}...A_{HW}

Output

Print the number of operations that will be performed.

Sample Input 1

3 3
...
.#.
...

Sample Output 1

2

After one operation, all but the corners of the grid will be black. After one more operation, all the squares will be black.

Sample Input 2

6 6
..#..#
......
#..#..
......
.#....
....#.

Sample Output 2

3


import java.io.OutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintWriter;
import java.util.Arrays;
import java.util.Scanner;
import java.util.ArrayDeque;

/**
 * Built using CHelper plug-in
 * Actual solution is at the top
 *
 * @author silviase
 */
public class Main {
    public static void main(String[] args) {
        InputStream inputStream = System.in;
        OutputStream outputStream = System.out;
        Scanner in = new Scanner(inputStream);
        PrintWriter out = new PrintWriter(outputStream);
        ADarkerAndDarker solver = new ADarkerAndDarker();
        solver.solve(1, in, out);
        out.close();
    }

    static class ADarkerAndDarker {
        public void solve(int testNumber, Scanner in, PrintWriter out) {
            final int INF = (int) 1e9;
            int[] dx = {-1, 0, 0, 1};
            int[] dy = {0, -1, 1, 0};

            int h = in.nextInt();
            int w = in.nextInt();

            char[][] c = new char[h][w];
            for (int i = 0; i < h; i++) {
                c[i] = in.next().toCharArray();
            }
            int[][] man = new int[h + 1][w + 1];
            boolean[][] used = new boolean[h + 1][w + 1];

            for (int i = 0; i <= h; i++) {
                Arrays.fill(man[i], INF);
                Arrays.fill(used[i], false);
            }
            ArrayDeque<Integer> q = new ArrayDeque<>();

            for (int i = 0; i < h; i++) {
                for (int j = 0; j < w; j++) {
                    // 多点bfs
                    if (c[i][j] == '#') {
                        q.addLast(i * w + j);
                        used[i][j] = true;
                        man[i][j] = 0;
                    }
                }
            }

            while (!q.isEmpty()) {
                //out.println("q:" + q.peek());
                int now = q.pollFirst();
                for (int i = 0; i < 4; i++) {
                    int x = now / w;
                    int y = now % w;
                    int nx = x + dx[i];
                    int ny = y + dy[i];
                    if (!(0 <= nx && nx < h && 0 <= ny && ny < w)) continue;
                    if (!used[nx][ny]) {
                        used[nx][ny] = true;
                        man[nx][ny] = man[x][y] + 1;
                        q.addLast(nx * w + ny);
                    }
                }
            }

            int max = -1;
            for (int i = 0; i < h; i++) {
                // out.println(Arrays.toString(man[i]));
                for (int j = 0; j < w; j++) {
                    max = Math.max(max, man[i][j]);
                }
            }

            out.println(max);

        }

    }
}


import java.io.InputStream;
import java.io.IOException;
import java.io.PrintStream;
import java.util.*;
import java.util.function.BiPredicate;
import java.util.function.IntFunction;
import java.util.function.ToIntFunction;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import java.util.stream.Stream;

public class Main {

  public static void main(String[] args) {
    solve(System.in, System.out);
  }

  private static DistanceMemoBfsPathQuery query;
  private static EncodedGraph<GridPoint> graph;

  static void solve(InputStream is, PrintStream os) {
    Scanner sc = new Scanner(is);

    /* read */
    int h = sc.nextInt();
    int w = sc.nextInt();

    List<Integer> blacks = new ArrayList<>();
    GridPointEncoder encoder = new GridPointEncoder(h, w);
    for (int r = 0; r < h; r++) {
      String line = sc.next();
      for (int c = 0; c < w; c++) {
        if (line.charAt(c) == '#') {
          blacks.add(encoder.encode(new GridPoint(r, c)));
        }
      }
    }

    // reduce visited cells
    graph = new VerticalHorizontalNeighborGridPointGraph(h, w, (r, c) -> true);

    query = new DistanceMemoBfsPathQuery(graph.delegate);

    query.path(blacks.stream().mapToInt(i -> i).toArray(), -1);

    os.println(max);

  }

//  private static boolean predicate(int r, int c, int h, int w) {
//    return !query.visited[r * w + c];
//  }

  static long max = 0L;

  private static class DistanceMemoBfsPathQuery extends BfsPathQuery {

    public DistanceMemoBfsPathQuery(IntGraph graph) {
      super(graph);
    }

    @Override
    int mark(Path path, int to) {
      max = Math.max(max, path.getWeight() + graph.getWeight(path.getEnd(), to));
      return super.mark(path, to);
    }
  }

  private static class GridPointEncoder implements Encoder<GridPoint> {

    public GridPointEncoder(int h, int w) {
      this.h = h;
      this.w = w;
    }

    private final int h;
    private final int w;

    @Override
    public int num() {
      return w * h;
    }

    @Override
    public int encode(GridPoint from) {
      if (from == null) {
        return -1;
      }
      return w * from.r + from.c;
    }

    @Override
    public GridPoint decode(int from) {
      if (from < 0) {
        return null;
      }
      return new GridPoint(from / w, from % w);
    }
  }

  private static class GridPoint implements Encoded<GridPoint> {

    private final int r;
    private final int c;

    public GridPoint(int r, int c) {
      this.r = r;
      this.c = c;
    }
  }

  private static class VerticalHorizontalNeighborGridPointGraph extends GridPointGraph {

    private final BiPredicate<Integer, Integer> predicate;
    protected List<int[]> moves = Arrays.asList(
        new int[]{-1, 0},
        new int[]{1, 0},
        new int[]{0, -1},
        new int[]{0, 1}
    );

    public VerticalHorizontalNeighborGridPointGraph(int h, int w,
        BiPredicate<Integer, Integer> predicate) {
      super(h, w);

      this.predicate = predicate;

      this.delegate = new IntGraph() {
        @Override
        public int getSize() {
          return w * h;
        }

        @Override
        public IntStream vertices() {
          return IntStream.range(0, w * h);
        }

        @Override
        public IntStream adjacentVertices(int from) {
          return neighbors(decoder.apply(from)).mapToInt(encoder);
        }

        @Override
        public int getWeight(int from, int to) {
          return 1;
        }
      };
    }

    protected boolean predicate(int r, int c) {
      if (r < 0 || h <= r) {
        return false;
      }
      if (c < 0 || w <= c) {
        return false;
      }
      return predicate.test(r, c);
    }

    protected Stream<GridPoint> neighbors(GridPoint from) {

      return moves.stream().filter(move -> predicate(from.r + move[0], from.c + move[1]))
          .map(move -> new GridPoint(from.r + move[0], from.c + move[1]));
    }
  }

//  private static class AdjacencyMatrixGridPointGraph extends IntVertexGraph {
//
//    private List<Integer> vertexes;
//
//    private final int n;
//    private final int[][] matrix;
//
//    public AdjacencyMatrixGridPointGraph(int n, int[][] matrix) {
//      this.n = n;
//      this.matrix = matrix;
//    }
//
//    @Override
//    public Collection<Integer> getVertices() {
//      // lazy load
//      if (vertexes == null) {
//        vertexes = IntStream.range(0, n).boxed().collect(Collectors.toList());
//      }
//      return vertexes;
//    }
//
//    @Override
//    public int getSize() {
//      return n;
//    }
//
//    @Override
//    public List<Edge<Integer>> getEdges(Integer from) {
//      return IntStream.range(0, n).mapToObj(to -> new Edge<>(from, to, matrix[from][to]))
//          .collect(Collectors.toList());
//    }
//  }
//
//  private static class AdjacencyListGridPointGraph extends IntVertexGraph {
//
//    private List<Integer> vertexes;
//
//    private final int n;
//    private final List<List<Edge<Integer>>> edges;
//
//    public AdjacencyListGridPointGraph(int n, List<List<Edge<Integer>>> edges) {
//      this.n = n;
//      this.edges = edges;
//    }
//
//    @Override
//    public Collection<Integer> getVertices() {
//      // lazy load
//      if (vertexes == null) {
//        vertexes = IntStream.range(0, n).boxed().collect(Collectors.toList());
//      }
//      return vertexes;
//    }
//
//    @Override
//    public int getSize() {
//      return n;
//    }
//
//    @Override
//    public List<Edge<Integer>> getEdges(Integer from) {
//      return edges.get(from);
//    }
//  }

  private static abstract class GridPointGraph extends EncodedGraph<GridPoint> {

    public GridPointGraph(int h, int w) {
      super(new GridPointEncoder(h, w)::encode, new GridPointEncoder(h, w)::decode);
      this.h = h;
      this.w = w;
    }

    protected final int h;
    protected final int w;

  }

  private static interface Encoded<E extends Encoded> {

  }

  private static interface Encoder<E extends Encoded> {

    int num();

    int encode(E from);

    E decode(int from);
  }

//  private static abstract class EncodedGraph<V extends Encoded<V>> implements Graph<V> {
//
//    public EncodedGraph(Encoder<V> encoder) {
//      this.encoder = encoder;
//      this.delegate = new IntVertexGraph() {
//        @Override
//        public Collection<Integer> getVertices() {
//          return EncodedGraph.this.getVertices().stream()
//              .map(encoder::encode)
//              .collect(Collectors.toList());
//        }
//
//        @Override
//        public Collection<Integer> getAdjacentVertices(Integer from) {
//          Collection<V> adjacent = EncodedGraph.this.getAdjacentVertices(encoder.decode(from));
//
//          List<Integer> ans = new ArrayList<>(adjacent.size());
//          for (V v : adjacent) {
//            ans.add(encoder.encode(v));
//          }
//          return ans;
//        }
//
//        @Override
//        public int getWeight(Integer from, Integer to) {
//          return EncodedGraph.this.getWeight(encoder.decode(from), encoder.decode(to));
//        }
//
//        @Override
//        public int getSize() {
//          return EncodedGraph.this.getSize();
//        }
//
////        @Override
////        public Collection<Edge<Integer>> getEdges(Integer from) {
////          Collection<Edge<V>> edges = EncodedGraph.this.getEdges(encoder.decode(from));
////          List<Edge<Integer>> ans = new ArrayList<>(edges.size());
////          for (Edge<V> e : edges) {
////            ans.add(new Edge<>(
////                encoder.encode(e.getFrom()), encoder.encode(e.getTo()), e.getWeight()));
////          }
////          return ans;
////        }
//      };
//    }
//
//    final Encoder<V> encoder;
//    final IntVertexGraph delegate;
//
//    public PathQuery<V> pathQuery(Function<Graph<Integer>, PathQuery<Integer>> delegate) {
//      return new EncodedPathQuery<>(delegate, this);
//    }
//
//    public MultiPathQuery<V> multiPathQuery(
//        Function<Graph<Integer>, MultiPathQuery<Integer>> delegate) {
//      return new EncodedMultiPathQuery<>(delegate, this);
//    }
//
//    private static class EncodedPathQuery<V extends Encoded<V>> implements PathQuery<V> {
//
//      EncodedGraph<V> graph;
//      PathQuery delegate;
//
//      private EncodedPathQuery(Function<Graph<Integer>, PathQuery> delegate,
//          EncodedGraph<V> graph) {
//        this.graph = graph;
//        this.delegate = delegate.apply(graph.delegate);
//      }
//
//      @Override
//      public VertexPath<V> path(V begin, V end) {
//        VertexPath<Integer> path = delegate
//            .path(graph.encoder.encode(begin), graph.encoder.encode(end));
//        if (path == null) {
//          return null;
//        }
//        return new EfficientVertexPath<>(graph.encoder.decode(path.getBegin()),
//            graph.encoder.decode(path.getEnd()), path.getWeight());
//      }
//    }
//
//    private static class EncodedMultiPathQuery<V extends Encoded<V>> implements MultiPathQuery<V> {
//
//      EncodedGraph<V> graph;
//      MultiPathQuery<Integer> delegate;
//
//      private EncodedMultiPathQuery(Function<Graph<Integer>, MultiPathQuery<Integer>> delegate,
//          EncodedGraph<V> graph) {
//        this.graph = graph;
//        this.delegate = delegate.apply(graph.delegate);
//      }
//
//      @Override
//      public VertexPath<V> path(List<V> begin, V end) {
//        List<Integer> list = begin.stream().map(graph.encoder::encode)
//            .collect(Collectors.toList());
//        VertexPath<Integer> path = delegate
//            .path(list, graph.encoder.encode(end));
//        if (path == null) {
//          return null;
//        }
//        return new EfficientVertexPath<>(graph.encoder.decode(path.getBegin()),
//            graph.encoder.decode(path.getEnd()), path.getWeight());
//      }
//    }
//
//  }

  private static class EncodedGraph<V> implements Graph<V> {

    // protected
    IntGraph delegate;
    protected final ToIntFunction<V> encoder;
    protected final IntFunction<V> decoder;

    private EncodedGraph(ToIntFunction<V> encoder, IntFunction<V> decoder) {
      this.encoder = encoder;
      this.decoder = decoder;
    }

    @Override
    public int getSize() {
      return delegate.getSize();
    }

    @Override
    public Stream<V> vertices() {
      return delegate.vertices().mapToObj(decoder);
    }

    @Override
    public Stream<V> adjacentVertices(V from) {
      return delegate.adjacentVertices(encoder.applyAsInt(from)).mapToObj(decoder);
    }

    @Override
    public int getWeight(V from, V to) {
      return delegate.getWeight(encoder.applyAsInt(from), encoder.applyAsInt(to));
    }
  }

  private static interface EncodedPathQuery<V> {

    VertexPath<V> path(V begin, V end);
  }

  private static interface EncodedMultiPathQuery<V> {

    VertexPath<V> path(List<V> begin, V end);
  }

  private static interface PathQuery {

    Path path(int begin, int end);
  }

  private static interface MultiPathQuery {

    Path path(int[] begin, int end);
  }

  private static abstract class QueuedPathQuery implements PathQuery {

    protected final IntGraph graph;
    protected final Queue<Path> queue;

    public QueuedPathQuery(IntGraph graph, Queue<Path> queue) {
      this.graph = graph;
      this.queue = queue;
    }

    public Path path(int begin, int end) {

      prepare(begin, end);
      queue.add(new IntPath(begin));
      return search(end);
    }

    protected Path search(int end) {
      while (!queue.isEmpty()) {
        Path path = queue.remove();
        int head = path.getEnd();

        if (head == end) {
          return new IntPath(path.getBegin(),
              path.getEnd(), path.getWeight());
        }

        graph.adjacentVertices(head).filter(to -> predicate(path, to))
            .map(to -> mark(path, to))
            .mapToObj(to -> path.append(to, graph.getWeight(head, to)))
            .forEach(queue::add);
      }
      return null;
    }

    abstract void prepare(int begin, int end);

    abstract boolean predicate(Path path, int to);

    abstract int mark(Path path, int to);
  }

  private static class BfsPathQuery extends QueuedPathQuery implements
      MultiPathQuery {

    final boolean[] visited;

    public BfsPathQuery(IntGraph graph) {
      super(graph, new ArrayDeque<>());
      visited = new boolean[graph.getSize()];
    }

    @Override
    void prepare(int begin, int end) {
      visited[begin] = true;
    }

    @Override
    boolean predicate(Path path, int to) {
      return !visited[to];
    }

    @Override
    int mark(Path path, int to) {
      visited[to] = true;
      return to;
    }

    @Override
    public Path path(int[] begins, int end) {

      for (int begin : begins) {
        prepare(begin, end);
        queue.add(new IntPath(begin));
      }
      return search(end);
    }

  }

//  private static abstract class QueuedPathQuery<V> implements PathQuery<V> {
//
//    protected final IntGraph graph;
//    protected final Queue<Path> queue;
//
//    private final ToIntFunction<V> encoder;
//    private final IntFunction<V> decoder;
//
//    public QueuedPathQuery(IntGraph graph, Queue<Path> queue, ToIntFunction<V> encoder,
//        IntFunction<V> decoder) {
//      this.graph = graph;
//      this.queue = queue;
//      this.encoder = encoder;
//      this.decoder = decoder;
//    }
//
//    public VertexPath<V> path(V begin, V end) {
//
//      prepare(encoder.applyAsInt(begin), encoder.applyAsInt(end));
//      queue.add(new IntPath<>(encoder.applyAsInt(begin)));
//      return search(encoder.applyAsInt(end));
//    }
//
//    protected VertexPath<V> search(int end) {
//      while (!queue.isEmpty()) {
//        Path path = queue.remove();
//        int head = path.getEnd();
//
//        if (head == end) {
//          return new EfficientVertexPath<>(decoder.apply(path.getBegin()),
//              decoder.apply(path.getEnd()), path.getWeight());
//        }
//
//        int[] adjacent = graph.getAdjacentVertices(head);
//        for (int to : adjacent) {
//          if (!predicate(path, to)) {
//            continue;
//          }
//          mark(path, to);
//          queue.add(path.append(to, graph.getWeight(head, to)));
//        }
//      }
//      return null;
//    }
//
//    abstract void prepare(int begin, int end);
//
//    abstract boolean predicate(Path path, int to);
//
//    abstract void mark(Path path, int to);
//  }

//  private static class DijkstraPathQuery extends QueuedPathQuery<Integer> {
//
//    private final long[] distance;
//
//    public DijkstraPathQuery(Graph<Integer> graph) {
//      super(graph, new PriorityQueue<>(
//          Comparator.comparingLong(VertexPath::getWeight)));
//      distance = new long[graph.getSize()];
//    }
//
//
//    @Override
//    void prepare(Integer begin, Integer end) {
//      for (Integer v : graph.getVertices()) {
//        distance[v] = Integer.MAX_VALUE;
//      }
//    }
//
//    @Override
//    boolean predicate(VertexPath<Integer> path, Integer to) {
//      return distance[path.getEnd()] + graph.getWeight(path.getEnd(), to) < distance[to];
//    }
//
//    @Override
//    void mark(VertexPath<Integer> path, Integer to) {
//      distance[to] = distance[path.getEnd()] + graph.getWeight(path.getEnd(), to);
//    }
//  }

//  private static class BfsPathQuery extends QueuedPathQuery<Integer> implements
//      MultiPathQuery<Integer> {
//
//    final boolean[] visited;
//
//    public BfsPathQuery(IntGraph graph, ToIntFunction) {
//      super(graph, new ArrayDeque<>());
//      visited = new boolean[graph.getSize()];
//    }
//
//    @Override
//    void prepare(Integer begin, Integer end) {
//      visited[begin] = true;
//    }
//
//    @Override
//    boolean predicate(VertexPath<Integer> path, Integer to) {
//      return !visited[to];
//    }
//
//    @Override
//    void mark(VertexPath<Integer> path, Integer to) {
//      visited[to] = true;
//    }
//
//    public VertexPath<Integer> path(List<Integer> begins, Integer end) {
//
//      for (int begin : begins) {
//        prepare(begin, end);
//        queue.add(new EfficientVertexPath<>(begin));
//      }
//      return search(end);
//    }
//
//  }

//  private static class BfsPathQuery extends QueuedPathQuery<Integer> implements
//      MultiPathQuery<Integer> {
//
//    final boolean[] visited;
//
//    public BfsPathQuery(Graph<Integer> graph) {
//      super(graph, new ArrayDeque<>());
//      visited = new boolean[graph.getSize()];
//    }
//
//    @Override
//    void prepare(Integer begin, Integer end) {
//      visited[begin] = true;
//    }
//
//    @Override
//    boolean predicate(Edge<Integer> edge) {
//      return !visited[edge.getTo()];
//    }
//
//    @Override
//    void mark(VertexPath<Integer> path, Edge<Integer> edge) {
//      visited[edge.getTo()] = true;
//    }
//
//    public VertexPath<Integer> path(List<Integer> begins, Integer end) {
//
//      for (int begin : begins) {
//        prepare(begin, end);
//        queue.add(new EfficientVertexPath<>(begin));
//      }
//      return search(end);
//    }
//
//  }

//  private static abstract class MemoDfsPathQuery<V> extends DfsPathQuery<V> {
//
//    public MemoDfsPathQuery(Graph<V> graph) {
//      super(graph);
//    }
//
//    abstract VertexPath<V> memo(VertexPath<V> path, V parent,
//        V end);
//
//    @Override
//    public VertexPath<V> path(VertexPath<V> path, V parent,
//        V end) {
//      VertexPath<V> memo = memo(path, parent, end);
//      if (memo != null) {
//        return memo;
//      }
//      return super.path(path, parent, end);
//    }
//  }

//  private static class DfsPathQuery<V> implements PathQuery<V> {
//
//    protected final Graph<V> graph;
//
//    public DfsPathQuery(Graph<V> graph) {
//      this.graph = graph;
//    }
//
//    @Override
//    public VertexPath<V> path(V begin, V end) {
//      return path(new EfficientVertexPath<>(begin), null, end);
//    }
//
//    protected VertexPath<V> path(VertexPath<V> path, V parent,
//        V end) {
//      V head = path.getEnd();
//      if (path.getEnd().equals(end)) {
//        return path;
//      }
//      Collection<V> adjacent = graph.getAdjacentVertices(head);
//      for (V to : adjacent) {
//        if (!to.equals(parent)) {
//          VertexPath<V> next = path(path.append(to, graph.getWeight(head, to)), head, end);
//          if (next != null) {
//            return next;
//          }
//        }
//      }
//      return null;
//    }
//  }

//  private static class WarshallFloydQuery implements PathQuery<Integer> {
//
//    private VertexPath<Integer>[][] shortest;
//
//    private WarshallFloydQuery(Graph<Integer> graph) {
//      Collection<Integer> nodes = graph.getVertices();
//
//      shortest = new VertexPath[nodes.size()][nodes.size()];
//
//      for (int from : nodes) {
//        Collection<Edge<Integer>> edges = graph.getEdges(from);
//        for (Edge<Integer> e : edges) {
//          shortest[e.getFrom()][e.getTo()] = new EfficientVertexPath<>(e);
//        }
//        shortest[from][from] = new EfficientVertexPath<>(from);
//      }
//
//      for (int relay : nodes) {
//        for (int from : nodes) {
//          for (int dest : nodes) {
//            VertexPath<Integer> pathA = shortest[from][relay];
//            VertexPath<Integer> pathB = shortest[relay][dest];
//            if (pathA != null && pathB != null) {
//              if (shortest[from][dest] == null
//                  || pathA.getWeight() + pathB.getWeight() < shortest[from][dest]
//                  .getWeight()) {
//                shortest[from][dest] = pathA.append(pathB);
//              }
//            }
//          }
//        }
//      }
//    }
//
//    @Override
//    public VertexPath<Integer> path(Integer begin, Integer end) {
//      return shortest[begin][end];
//    }
//  }

//  private static abstract class IntVertexGraph implements Graph<Integer> {
//
//  }

//  static class Edge<V> {
//
//    public Edge(V from, V to, int weight) {
//      this.from = from;
//      this.to = to;
//      this.weight = weight;
//    }
//
//    private final V from;
//
//    private final V to;
//    private final int weight;
//
//
//    V getFrom() {
//      return from;
//    }
//
//    V getTo() {
//      return to;
//    }
//
//    int getWeight() {
//      return weight;
//    }
//  }

  static interface VertexPath<V> {

    V getBegin();

    V getEnd();

    int getWeight();

    VertexPath<V> append(VertexPath<V> other);

    void merge(VertexPath<V> other);

    VertexPath<V> append(V to, int weight);

//    void merge(Edge<V> edge);
  }

  static class EfficientVertexPath<V> implements VertexPath<V> {

    private V begin;
    private V end;
    private int weight;

    public EfficientVertexPath(V begin) {
      this.begin = begin;
      this.end = begin;
      this.weight = 0;
    }

    public EfficientVertexPath(V begin, V end, int weight) {
      this.begin = begin;
      this.end = end;
      this.weight = weight;
    }

//    public EfficientVertexPath(Edge<V> edge) {
//      this.begin = edge.getFrom();
//      this.end = edge.getTo();
//      this.weight = edge.getWeight();
//    }
//
//    private EfficientVertexPath(VertexPath<V> path, Edge<V> append) {
//      this.begin = path.getBegin();
//      if (!path.getEnd().equals(append.getFrom())) {
//        throw new IllegalStateException("not correct edge.");
//      }
//      this.end = append.getTo();
//      this.weight = path.getWeight() + append.getWeight();
//    }

    private EfficientVertexPath(VertexPath<V> pathA, VertexPath<V> pathB) {
      this.begin = pathA.getBegin();
      if (!pathA.getEnd().equals(pathB.getBegin())) {
        throw new IllegalStateException("not correct edge.");
      }
      this.end = pathB.getEnd();
      this.weight = pathA.getWeight() + pathB.getWeight();
    }

    @Override
    public V getBegin() {
      return begin;
    }

    @Override
    public V getEnd() {
      return end;
    }

    @Override
    public int getWeight() {
      return weight;
    }

    @Override
    public VertexPath<V> append(VertexPath<V> other) {
      return new EfficientVertexPath<>(this, other);
    }

    @Override
    public void merge(VertexPath<V> other) {
      this.end = other.getEnd();
      this.weight += other.getWeight();
    }

    @Override
    public VertexPath<V> append(V to, int weight) {
      return new EfficientVertexPath<>(this.begin, to, this.weight + weight);
    }

//    @Override
//    public VertexPath<V> append(Edge<V> edge) {
//      return new EfficientVertexPath<>(this, edge);
//    }
//
//    @Override
//    public void merge(Edge<V> edge) {
//      this.end = edge.getTo();
//      this.weight += edge.getWeight();
//    }
  }

  private static interface Graph<V> {

    int getSize();

    Stream<V> vertices();

    Stream<V> adjacentVertices(V from);

    int getWeight(V from, V to);
  }

  private static interface IntGraph {

    int getSize();

    IntStream vertices();

    IntStream adjacentVertices(int from);

    int getWeight(int from, int to);
  }

  static interface Path {

    int getBegin();

    int getEnd();

    int getWeight();

    Path append(Path other);

    void merge(Path other);

    Path append(int to, int weight);
  }

  static class IntPath implements Path {

    private int begin;
    private int end;
    private int weight;

    public IntPath(int begin) {
      this.begin = begin;
      this.end = begin;
      this.weight = 0;
    }

    public IntPath(int begin, int end, int weight) {
      this.begin = begin;
      this.end = end;
      this.weight = weight;
    }

//    public EfficientVertexPath(Edge<V> edge) {
//      this.begin = edge.getFrom();
//      this.end = edge.getTo();
//      this.weight = edge.getWeight();
//    }
//
//    private EfficientVertexPath(VertexPath<V> path, Edge<V> append) {
//      this.begin = path.getBegin();
//      if (!path.getEnd().equals(append.getFrom())) {
//        throw new IllegalStateException("not correct edge.");
//      }
//      this.end = append.getTo();
//      this.weight = path.getWeight() + append.getWeight();
//    }

    private IntPath(Path pathA, Path pathB) {
      this.begin = pathA.getBegin();
      if (pathA.getEnd() != pathB.getBegin()) {
        throw new IllegalStateException("not correct edge.");
      }
      this.end = pathB.getEnd();
      this.weight = pathA.getWeight() + pathB.getWeight();
    }

    @Override
    public int getBegin() {
      return begin;
    }

    @Override
    public int getEnd() {
      return end;
    }

    @Override
    public int getWeight() {
      return weight;
    }

    @Override
    public Path append(Path other) {
      return new IntPath(this, other);
    }

    @Override
    public void merge(Path other) {
      this.end = other.getEnd();
      this.weight += other.getWeight();
    }

    @Override
    public Path append(int to, int weight) {
      return new IntPath(this.begin, to, this.weight + weight);
    }
  }

  private static class Scanner {

    private final InputStream is;
    private final byte[] buffer = new byte[1024];
    private int ptr = 0;
    private int buflen = 0;

    Scanner(InputStream is) {
      this.is = is;
    }

    private boolean hasNextByte() {
      if (ptr < buflen) {
        return true;
      } else {
        ptr = 0;
        try {
          buflen = is.read(buffer);
        } catch (IOException e) {
          e.printStackTrace();
        }
        if (buflen <= 0) {
          return false;
        }
      }
      return true;
    }

    private int readByte() {
      if (hasNextByte()) {
        return buffer[ptr++];
      } else {
        return -1;
      }
    }

    private static boolean isPrintableChar(int c) {
      return 33 <= c && c <= 126;
    }

    public boolean hasNext() {
      while (hasNextByte() && !isPrintableChar(buffer[ptr])) {
        ptr++;
      }
      return hasNextByte();
    }

    public String next() {
      if (!hasNext()) {
        throw new NoSuchElementException();
      }
      StringBuilder sb = new StringBuilder();
      int b = readByte();
      while (isPrintableChar(b)) {
        sb.appendCodePoint(b);
        b = readByte();
      }
      return sb.toString();
    }

    public long nextLong() {
      if (!hasNext()) {
        throw new NoSuchElementException();
      }
      long n = 0;
      boolean minus = false;
      int b = readByte();
      if (b == '-') {
        minus = true;
        b = readByte();
      }
      if (b < '0' || '9' < b) {
        throw new NumberFormatException();
      }
      while (true) {
        if ('0' <= b && b <= '9') {
          n *= 10;
          n += b - '0';
        } else if (b == -1 || !isPrintableChar(b)) {
          return minus ? -n : n;
        } else {
          throw new NumberFormatException();
        }
        b = readByte();
      }
    }

    public int nextInt() {
      long nl = nextLong();
      if (nl < Integer.MIN_VALUE || nl > Integer.MAX_VALUE) {
        throw new NumberFormatException();
      }
      return (int) nl;
    }

    public double nextDouble() {
      return Double.parseDouble(next());
    }
  }
}


import java.io.InputStream;
import java.io.IOException;
import java.io.PrintStream;
import java.util.*;
import java.util.function.BiPredicate;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import java.util.stream.IntStream;

public class Main {

  public static void main(String[] args) {
    solve(System.in, System.out);
  }

  private static DistanceMemoBfsPathQuery query;
  private static EncodedGraph<GridPoint> graph;

  static void solve(InputStream is, PrintStream os) {
    Scanner sc = new Scanner(is);

    /* read */
    int h = sc.nextInt();
    int w = sc.nextInt();

    List<GridPoint> blacks = new ArrayList<>();
    for (int r = 0; r < h; r++) {
      String line = sc.next();
      for (int c = 0; c < w; c++) {
        if (line.charAt(c) == '#') {
          blacks.add(new GridPoint(r, c));
        }
      }
    }

    // reduce visited cells
    graph = new VerticalHorizontalNeighborGridPointGraph(h, w, (r, c) -> predicate(r, c, h, w));

    MultiPathQuery<GridPoint> query = graph.multiPathQuery(Main::sneaky);

    query.path(blacks, null);

    os.println(max);

  }

  private static boolean predicate(int r, int c, int h, int w) {
    return !query.visited[r * w + c];
  }

  private static DistanceMemoBfsPathQuery sneaky(Graph<Integer> delegate) {
    query = new DistanceMemoBfsPathQuery(delegate);
    return query;
  }

  static long max = 0L;

  private static class DistanceMemoBfsPathQuery extends BfsPathQuery {

    public DistanceMemoBfsPathQuery(Graph<Integer> graph) {
      super(graph);
    }

    @Override
    void mark(Graph.VertexPath<Integer> existing, Graph.Edge<Integer> edge,
        Graph.VertexPath<Integer> path) {
      super.mark(existing, edge, path);
      max = Math.max(max, path.getWeight());
    }
  }

  private static class GridPointEncoder implements Encoder<GridPoint> {

    public GridPointEncoder(int h, int w) {
      this.h = h;
      this.w = w;
    }

    private final int h;
    private final int w;

    @Override
    public int num() {
      return w * h;
    }

    @Override
    public Integer encode(GridPoint from) {
      if (from == null) {
        return null;
      }
      return w * from.r + from.c;
    }

    @Override
    public GridPoint decode(Integer from) {
      if (from == null) {
        return null;
      }
      return new GridPoint(from / w, from % w);
    }
  }

  private static class GridPoint implements Encoded<GridPoint> {

    private final int r;
    private final int c;

    public GridPoint(int r, int c) {
      this.r = r;
      this.c = c;
    }

    @Override
    public boolean equals(Object o) {
      if (this == o) {
        return true;
      }
      if (o == null || getClass() != o.getClass()) {
        return false;
      }
      GridPoint gridPoint = (GridPoint) o;
      return r == gridPoint.r &&
          c == gridPoint.c;
    }

    @Override
    public int hashCode() {
      return Objects.hash(r, c);
    }
  }

  private static class VerticalHorizontalNeighborGridPointGraph extends GridPointGraph {

    private final BiPredicate<Integer, Integer> predicate;
    protected List<int[]> moves = Arrays.asList(
        new int[]{-1, 0},
        new int[]{1, 0},
        new int[]{0, -1},
        new int[]{0, 1}
    );

    public VerticalHorizontalNeighborGridPointGraph(int h, int w,
        BiPredicate<Integer, Integer> predicate) {
      super(h, w);
      this.predicate = predicate;
    }

    protected boolean predicate(int r, int c) {
      if (r < 0 || h <= r) {
        return false;
      }
      if (c < 0 || w <= c) {
        return false;
      }
      return predicate.test(r, c);
    }

    protected List<Edge<GridPoint>> neighbors(GridPoint from) {

      List<Edge<GridPoint>> ans = new ArrayList<>();

      for (int i = 0; i < 4; i++) {
        int[] move = moves.get(i);
        if (predicate(from.r + move[0], from.c + move[1])) {
          ans.add(new ConstantWeightEdge<>(from, new GridPoint(from.r + move[0], from.c + move[1])));
        }
      }

      return ans;
    }

    @Override
    public int getSize() {
      return h * w;
    }

    @Override
    public Collection<Edge<GridPoint>> getEdges(GridPoint from) {
      return neighbors(from);
    }
  }

  private static class AdjacencyMatrixGridPointGraph extends IntVertexGraph {

    private List<Integer> vertexes;

    private final int n;
    private final int[][] matrix;

    public AdjacencyMatrixGridPointGraph(int n, int[][] matrix) {
      this.n = n;
      this.matrix = matrix;
    }

    @Override
    public Collection<Integer> getVertexes() {
      // lazy load
      if (vertexes == null) {
        vertexes = IntStream.range(0, n).boxed().collect(Collectors.toList());
      }
      return vertexes;
    }

    @Override
    public int getSize() {
      return n;
    }

    @Override
    public List<Edge<Integer>> getEdges(Integer from) {
      return IntStream.range(0, n).mapToObj(to -> new Edge<>(from, to, matrix[from][to]))
          .collect(Collectors.toList());
    }
  }

  private static class AdjacencyListGridPointGraph extends IntVertexGraph {

    private List<Integer> vertexes;

    private final int n;
    private final List<List<Edge<Integer>>> edges;

    public AdjacencyListGridPointGraph(int n, List<List<Edge<Integer>>> edges) {
      this.n = n;
      this.edges = edges;
    }

    @Override
    public Collection<Integer> getVertexes() {
      // lazy load
      if (vertexes == null) {
        vertexes = IntStream.range(0, n).boxed().collect(Collectors.toList());
      }
      return vertexes;
    }

    @Override
    public int getSize() {
      return n;
    }

    @Override
    public List<Edge<Integer>> getEdges(Integer from) {
      return edges.get(from);
    }
  }

  private static abstract class GridPointGraph extends EncodedGraph<GridPoint> {

    public GridPointGraph(int h, int w) {
      super(new GridPointEncoder(h, w));
      this.h = h;
      this.w = w;
    }

    protected final int h;
    protected final int w;

    protected List<GridPoint> vertexes;

    @Override
    public Collection<GridPoint> getVertexes() {

      // lazy load
      if (vertexes == null) {
        vertexes = new ArrayList<>(w * h);
        for (int r = 0; r < h; r++) {
          for (int c = 0; c < w; c++) {
            vertexes.add(new GridPoint(r, c));
          }
        }
      }

      return vertexes;
    }
  }

  private static interface Encoded<E extends Encoded> {

  }

  private static interface Encoder<E extends Encoded> {

    int num();

    Integer encode(E from);

    E decode(Integer from);
  }

  private static abstract class EncodedGraph<V extends Encoded<V>> implements Graph<V> {

    public EncodedGraph(Encoder<V> encoder) {
      this.encoder = encoder;
      this.delegate = new IntVertexGraph() {
        @Override
        public Collection<Integer> getVertexes() {
          return EncodedGraph.this.getVertexes().stream()
              .map(encoder::encode)
              .collect(Collectors.toList());
        }

        @Override
        public int getSize() {
          return EncodedGraph.this.getSize();
        }

        @Override
        public Collection<Edge<Integer>> getEdges(Integer from) {
          Collection<Edge<V>> edges = EncodedGraph.this.getEdges(encoder.decode(from));
          List<Edge<Integer>> ans = new ArrayList<>(edges.size());
          for (Edge<V> e : edges) {
            ans.add(new Edge<>(
                encoder.encode(e.getFrom()), encoder.encode(e.getTo()), e.getWeight()));
          }
          return ans;
        }
      };
    }

    final Encoder<V> encoder;
    final IntVertexGraph delegate;

    public PathQuery<V> pathQuery(Function<Graph<Integer>, PathQuery<Integer>> delegate) {
      return new EncodedPathQuery<>(delegate, this);
    }

    public MultiPathQuery<V> multiPathQuery(
        Function<Graph<Integer>, MultiPathQuery<Integer>> delegate) {
      return new EncodedMultiPathQuery<>(delegate, this);
    }

    private static class EncodedPathQuery<V extends Encoded<V>> implements PathQuery<V> {

      EncodedGraph<V> graph;
      PathQuery<Integer> delegate;

      private EncodedPathQuery(Function<Graph<Integer>, PathQuery<Integer>> delegate,
          EncodedGraph<V> graph) {
        this.graph = graph;
        this.delegate = delegate.apply(graph.delegate);
      }

      @Override
      public VertexPath<V> path(V begin, V end) {
        VertexPath<Integer> path = delegate
            .path(graph.encoder.encode(begin), graph.encoder.encode(end));
        if (path == null) {
          return null;
        }
        return new EfficientVertexPath<>(graph.encoder.decode(path.getBegin()),
            graph.encoder.decode(path.getEnd()), path.getWeight());
      }
    }

    private static class EncodedMultiPathQuery<V extends Encoded<V>> implements MultiPathQuery<V> {

      EncodedGraph<V> graph;
      MultiPathQuery<Integer> delegate;

      private EncodedMultiPathQuery(Function<Graph<Integer>, MultiPathQuery<Integer>> delegate,
          EncodedGraph<V> graph) {
        this.graph = graph;
        this.delegate = delegate.apply(graph.delegate);
      }

      @Override
      public VertexPath<V> path(List<V> begin, V end) {
        List<Integer> list = begin.stream().map(graph.encoder::encode)
            .collect(Collectors.toList());
        VertexPath<Integer> path = delegate
            .path(list, graph.encoder.encode(end));
        if (path == null) {
          return null;
        }
        return new EfficientVertexPath<>(graph.encoder.decode(path.getBegin()),
            graph.encoder.decode(path.getEnd()), path.getWeight());
      }
    }

  }

  private static interface PathQuery<V> {

    Graph.VertexPath<V> path(V begin, V end);
  }

  private static interface MultiPathQuery<V> {

    Graph.VertexPath<V> path(List<V> begin, V end);
  }

  private static abstract class QueuedPathQuery<V> implements PathQuery<V> {

    protected final Graph<V> graph;
    protected final Queue<Graph.VertexPath<V>> queue;

    public QueuedPathQuery(Graph<V> graph, Queue<Graph.VertexPath<V>> queue) {
      this.graph = graph;
      this.queue = queue;
    }

    public Graph.VertexPath<V> path(V begin, V end) {

      prepare(begin, end);
      queue.add(new Graph.EfficientVertexPath<>(begin));
      return search(end);
    }

    protected Graph.VertexPath<V> search(V end) {
      while (!queue.isEmpty()) {
        Graph.VertexPath<V> path = queue.remove();
        V head = path.getEnd();

        if (head.equals(end)) {
          return path;
        }

        for (Graph.Edge<V> e : graph.getEdges(head)) {
          if (!predicate(e)) {
            continue;
          }
          Graph.VertexPath<V> p = path.append(e);
          queue.add(p);
          mark(path, e, p);
        }
      }
      return null;
    }

    abstract void prepare(V begin, V end);

    abstract boolean predicate(Graph.Edge<V> edge);

    abstract void mark(Graph.VertexPath<V> existing, Graph.Edge<V> edge,
        Graph.VertexPath<V> path);
  }

  private static class DijkstraPathQuery extends QueuedPathQuery<Integer> {

    private final long[] distance;

    public DijkstraPathQuery(Graph<Integer> graph) {
      super(graph, new PriorityQueue<>(
          Comparator.comparingLong(Graph.VertexPath::getWeight)));
      distance = new long[graph.getSize()];
    }

    @Override
    void prepare(Integer begin, Integer end) {
      for (Integer v : graph.getVertexes()) {
        distance[v] = Integer.MAX_VALUE;
      }
    }

    @Override
    boolean predicate(Graph.Edge<Integer> edge) {
      return distance[edge.getFrom()] + edge.getWeight() < distance[edge.getTo()];
    }

    @Override
    void mark(Graph.VertexPath<Integer> existing, Graph.Edge<Integer> edge,
        Graph.VertexPath<Integer> path) {
      distance[path.getEnd()] = path.getWeight();
    }
  }

  private static class BfsPathQuery extends QueuedPathQuery<Integer> implements
      MultiPathQuery<Integer> {

    final boolean[] visited;

    public BfsPathQuery(Graph<Integer> graph) {
      super(graph, new ArrayDeque<>());
      visited = new boolean[graph.getSize()];
    }

    @Override
    void prepare(Integer begin, Integer end) {
      visited[begin] = true;
    }

    @Override
    boolean predicate(Graph.Edge<Integer> edge) {
      return !visited[edge.getTo()];
    }

    @Override
    void mark(Graph.VertexPath<Integer> existing, Graph.Edge<Integer> edge,
        Graph.VertexPath<Integer> path) {
      visited[path.getEnd()] = true;
    }

    public Graph.VertexPath<Integer> path(List<Integer> begins, Integer end) {

      begins.forEach(begin -> prepare(begin, end));
      begins.forEach(begin -> queue.add(new Graph.EfficientVertexPath<>(begin)));
      return search(end);
    }

  }

  private static abstract class MemoDfsPathQuery<V> extends DfsPathQuery<V> {

    public MemoDfsPathQuery(Graph<V> graph) {
      super(graph);
    }

    abstract Graph.VertexPath<V> memo(Graph.VertexPath<V> path, V parent,
        V end);

    @Override
    public Graph.VertexPath<V> path(Graph.VertexPath<V> path, V parent,
        V end) {
      Graph.VertexPath<V> memo = memo(path, parent, end);
      if (memo != null) {
        return memo;
      }
      return super.path(path, parent, end);
    }
  }

  private static class DfsPathQuery<V> implements PathQuery<V> {

    protected final Graph<V> graph;

    public DfsPathQuery(Graph<V> graph) {
      this.graph = graph;
    }

    @Override
    public Graph.VertexPath<V> path(V begin, V end) {
      return path(new Graph.EfficientVertexPath<>(begin), null, end);
    }

    protected Graph.VertexPath<V> path(Graph.VertexPath<V> path, V parent,
        V end) {
      V head = path.getEnd();
      if (path.getEnd().equals(end)) {
        return path;
      }
      for (Graph.Edge<V> e : graph.getEdges(head)) {
        if (!e.getTo().equals(parent)) {
          Graph.VertexPath<V> next = path(path.append(e), head, end);
          if (next != null) {
            return next;
          }
        }
      }
      return null;
    }
  }

  private static class WarshallFloydQuery implements PathQuery<Integer> {

    private Graph.VertexPath<Integer>[][] shortest;

    private WarshallFloydQuery(Graph<Integer> graph) {
      Collection<Integer> nodes = graph.getVertexes();

      shortest = new Graph.VertexPath[nodes.size()][nodes.size()];

      for (int from : nodes) {
        Collection<Graph.Edge<Integer>> edges = graph.getEdges(from);
        for (Graph.Edge<Integer> e : edges) {
          shortest[e.getFrom()][e.getTo()] = new Graph.EfficientVertexPath<>(e);
        }
        shortest[from][from] = new Graph.EfficientVertexPath<>(from);
      }

      for (int relay : nodes) {
        for (int from : nodes) {
          for (int dest : nodes) {
            Graph.VertexPath<Integer> pathA = shortest[from][relay];
            Graph.VertexPath<Integer> pathB = shortest[relay][dest];
            if (pathA != null && pathB != null) {
              if (shortest[from][dest] == null
                  || pathA.getWeight() + pathB.getWeight() < shortest[from][dest]
                  .getWeight()) {
                shortest[from][dest] = pathA.append(pathB);
              }
            }
          }
        }
      }
    }

    @Override
    public Graph.VertexPath<Integer> path(Integer begin, Integer end) {
      return shortest[begin][end];
    }
  }

  private static abstract class IntVertexGraph implements Graph<Integer> {

  }

  private static interface Graph<V> {

    static class Edge<V> {

      public Edge(V from, V to, long weight) {
        this.from = from;
        this.to = to;
        this.weight = weight;
      }

      private final V from;

      private final V to;
      private final long weight;

      V getFrom() {
        return from;
      }

      V getTo() {
        return to;
      }

      long getWeight() {
        return weight;
      }

      @Override
      public boolean equals(Object o) {
        if (this == o) {
          return true;
        }
        if (o == null || getClass() != o.getClass()) {
          return false;
        }
        Edge<?> edge = (Edge<?>) o;
        return weight == edge.weight &&
            Objects.equals(from, edge.from) &&
            Objects.equals(to, edge.to);
      }

      @Override
      public int hashCode() {
        return Objects.hash(from, to, weight);
      }
    }

    static class ConstantWeightEdge<V> extends Edge<V> {

      public ConstantWeightEdge(V from, V to) {
        super(from, to, 1);
      }
    }

    Collection<V> getVertexes();

    int getSize();

    Collection<Edge<V>> getEdges(V from);

    static interface VertexPath<V> {

      V getBegin();

      V getEnd();

      long getWeight();

      VertexPath<V> append(VertexPath<V> other);

      VertexPath<V> append(Edge<V> edge);
    }

    static class EfficientVertexPath<V> implements VertexPath<V> {

      final V begin;
      final V end;
      final long weight;

      public EfficientVertexPath(V begin) {
        this.begin = begin;
        this.end = begin;
        this.weight = 0;
      }

      public EfficientVertexPath(V begin, V end, long weight) {
        this.begin = begin;
        this.end = end;
        this.weight = weight;
      }

      public EfficientVertexPath(Edge<V> edge) {
        this.begin = edge.getFrom();
        this.end = edge.getTo();
        this.weight = edge.getWeight();
      }

      private EfficientVertexPath(VertexPath<V> path, Edge<V> append) {
        this.begin = path.getBegin();
        if (!path.getEnd().equals(append.getFrom())) {
          throw new IllegalStateException("not correct edge.");
        }
        this.end = append.getTo();
        this.weight = path.getWeight() + append.getWeight();
      }

      private EfficientVertexPath(VertexPath<V> pathA, VertexPath<V> pathB) {
        this.begin = pathA.getBegin();
        if (!pathA.getEnd().equals(pathB.getBegin())) {
          throw new IllegalStateException("not correct edge.");
        }
        this.end = pathB.getEnd();
        this.weight = pathA.getWeight() + pathB.getWeight();
      }

      @Override
      public V getBegin() {
        return begin;
      }

      @Override
      public V getEnd() {
        return end;
      }

      @Override
      public long getWeight() {
        return weight;
      }

      @Override
      public VertexPath<V> append(VertexPath<V> other) {
        return new EfficientVertexPath<>(this, other);
      }

      @Override
      public VertexPath<V> append(Edge<V> edge) {
        return new EfficientVertexPath<>(this, edge);
      }

      @Override
      public boolean equals(Object o) {
        if (this == o) {
          return true;
        }
        if (o == null || getClass() != o.getClass()) {
          return false;
        }
        EfficientVertexPath<?> that = (EfficientVertexPath<?>) o;
        return weight == that.weight &&
            Objects.equals(begin, that.begin) &&
            Objects.equals(end, that.end);
      }

      @Override
      public int hashCode() {
        return Objects.hash(begin, end, weight);
      }
    }
  }

  private static class Scanner {

    private final InputStream is;
    private final byte[] buffer = new byte[1024];
    private int ptr = 0;
    private int buflen = 0;

    Scanner(InputStream is) {
      this.is = is;
    }

    private boolean hasNextByte() {
      if (ptr < buflen) {
        return true;
      } else {
        ptr = 0;
        try {
          buflen = is.read(buffer);
        } catch (IOException e) {
          e.printStackTrace();
        }
        if (buflen <= 0) {
          return false;
        }
      }
      return true;
    }

    private int readByte() {
      if (hasNextByte()) {
        return buffer[ptr++];
      } else {
        return -1;
      }
    }

    private static boolean isPrintableChar(int c) {
      return 33 <= c && c <= 126;
    }

    public boolean hasNext() {
      while (hasNextByte() && !isPrintableChar(buffer[ptr])) {
        ptr++;
      }
      return hasNextByte();
    }

    public String next() {
      if (!hasNext()) {
        throw new NoSuchElementException();
      }
      StringBuilder sb = new StringBuilder();
      int b = readByte();
      while (isPrintableChar(b)) {
        sb.appendCodePoint(b);
        b = readByte();
      }
      return sb.toString();
    }

    public long nextLong() {
      if (!hasNext()) {
        throw new NoSuchElementException();
      }
      long n = 0;
      boolean minus = false;
      int b = readByte();
      if (b == '-') {
        minus = true;
        b = readByte();
      }
      if (b < '0' || '9' < b) {
        throw new NumberFormatException();
      }
      while (true) {
        if ('0' <= b && b <= '9') {
          n *= 10;
          n += b - '0';
        } else if (b == -1 || !isPrintableChar(b)) {
          return minus ? -n : n;
        } else {
          throw new NumberFormatException();
        }
        b = readByte();
      }
    }

    public int nextInt() {
      long nl = nextLong();
      if (nl < Integer.MIN_VALUE || nl > Integer.MAX_VALUE) {
        throw new NumberFormatException();
      }
      return (int) nl;
    }

    public double nextDouble() {
      return Double.parseDouble(next());
    }
  }
}


import java.io.InputStream;
import java.io.IOException;
import java.io.PrintStream;
import java.util.*;
import java.util.function.BiPredicate;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import java.util.stream.IntStream;

public class Main {

  public static void main(String[] args) {
    solve(System.in, System.out);
  }

  private static DistanceMemoBfsPathQuery query;
  private static EncodedGraph<GridPoint> graph;

  static void solve(InputStream is, PrintStream os) {
    Scanner sc = new Scanner(is);

    /* read */
    int h = sc.nextInt();
    int w = sc.nextInt();

    List<GridPoint> blacks = new ArrayList<>();
    for (int r = 0; r < h; r++) {
      String line = sc.next();
      for (int c = 0; c < w; c++) {
        if (line.charAt(c) == '#') {
          blacks.add(new GridPoint(r, c));
        }
      }
    }

    // reduce visited cells
    graph = new VerticalHorizontalNeighborGridPointGraph(h, w, (r, c) -> predicate(r, c, h, w));

    MultiPathQuery<GridPoint> query = graph.multiPathQuery(Main::sneaky);

    query.path(blacks, null);

    os.println(max);

  }

  private static boolean predicate(Integer r, Integer c, Integer h, Integer w) {
    return !query.visited[r * w + c];
  }

  private static DistanceMemoBfsPathQuery sneaky(Graph<Integer> delegate) {
    query = new DistanceMemoBfsPathQuery(delegate);
    return query;
  }

  static long max = 0L;

  private static class DistanceMemoBfsPathQuery extends BfsPathQuery {

    public DistanceMemoBfsPathQuery(Graph<Integer> graph) {
      super(graph);
    }

    @Override
    void mark(Graph.VertexPath<Integer> existing, Graph.Edge<Integer> edge,
        Graph.VertexPath<Integer> path) {
      super.mark(existing, edge, path);
      max = Math.max(max, path.getWeight());
    }
  }

  private static class GridPointEncoder implements Encoder<GridPoint> {

    public GridPointEncoder(int h, int w) {
      this.h = h;
      this.w = w;
    }

    private final int h;
    private final int w;

    @Override
    public int num() {
      return w * h;
    }

    @Override
    public Integer encode(GridPoint from) {
      if (from == null) {
        return null;
      }
      return w * from.r + from.c;
    }

    @Override
    public GridPoint decode(Integer from) {
      if (from == null) {
        return null;
      }
      return new GridPoint(from / w, from % w);
    }
  }

  private static class GridPoint implements Encoded<GridPoint> {

    private final int r;
    private final int c;

    public GridPoint(int r, int c) {
      this.r = r;
      this.c = c;
    }

    @Override
    public boolean equals(Object o) {
      if (this == o) {
        return true;
      }
      if (o == null || getClass() != o.getClass()) {
        return false;
      }
      GridPoint gridPoint = (GridPoint) o;
      return r == gridPoint.r &&
          c == gridPoint.c;
    }

    @Override
    public int hashCode() {
      return Objects.hash(r, c);
    }
  }

  private static class VerticalHorizontalNeighborGridPointGraph extends GridPointGraph {

    private final BiPredicate<Integer, Integer> predicate;
    protected List<int[]> moves = Arrays.asList(
        new int[]{-1, 0},
        new int[]{1, 0},
        new int[]{0, -1},
        new int[]{0, 1}
    );

    public VerticalHorizontalNeighborGridPointGraph(int h, int w,
        BiPredicate<Integer, Integer> predicate) {
      super(h, w);
      this.predicate = predicate;
    }

    protected boolean predicate(Integer r, Integer c) {
      if (r < 0 || h <= r) {
        return false;
      }
      if (c < 0 || w <= c) {
        return false;
      }
      return predicate.test(r, c);
    }

    protected List<Edge<GridPoint>> neighbors(GridPoint from) {

      return moves.stream().filter(m -> predicate(from.r + m[0], from.c + m[1]))
          .map(m -> new ConstantWeightEdge<>(from, new GridPoint(from.r + m[0], from.c + m[1])))
          .collect(Collectors.toList());
//      List<Edge<GridPoint>> ans = new ArrayList<>();
//
//      for (int[] move : moves) {
//        if (predicate(from.r + move[0], from.c + move[1])) {
//          GridPoint to = new GridPoint(from.r + move[0], from.c + move[1]);
//          ans.add(new ConstantWeightEdge<>(from, to));
//        }
//      }
//      return ans;
    }

    @Override
    public int getSize() {
      return h * w;
    }

    @Override
    public Collection<Edge<GridPoint>> getEdges(GridPoint from) {
      return neighbors(from);
    }
  }

  private static class AdjacencyMatrixGridPointGraph extends IntVertexGraph {

    private List<Integer> vertexes;

    private final int n;
    private final int[][] matrix;

    public AdjacencyMatrixGridPointGraph(int n, int[][] matrix) {
      this.n = n;
      this.matrix = matrix;
    }

    @Override
    public Collection<Integer> getVertexes() {
      // lazy load
      if (vertexes == null) {
        vertexes = IntStream.range(0, n).boxed().collect(Collectors.toList());
      }
      return vertexes;
    }

    @Override
    public int getSize() {
      return n;
    }

    @Override
    public List<Edge<Integer>> getEdges(Integer from) {
      return IntStream.range(0, n).mapToObj(to -> new Edge<>(from, to, matrix[from][to]))
          .collect(Collectors.toList());
    }
  }

  private static class AdjacencyListGridPointGraph extends IntVertexGraph {

    private List<Integer> vertexes;

    private final int n;
    private final List<List<Edge<Integer>>> edges;

    public AdjacencyListGridPointGraph(int n, List<List<Edge<Integer>>> edges) {
      this.n = n;
      this.edges = edges;
    }

    @Override
    public Collection<Integer> getVertexes() {
      // lazy load
      if (vertexes == null) {
        vertexes = IntStream.range(0, n).boxed().collect(Collectors.toList());
      }
      return vertexes;
    }

    @Override
    public int getSize() {
      return n;
    }

    @Override
    public List<Edge<Integer>> getEdges(Integer from) {
      return edges.get(from);
    }
  }

  private static abstract class GridPointGraph extends EncodedGraph<GridPoint> {

    public GridPointGraph(int h, int w) {
      super(new GridPointEncoder(h, w));
      this.h = h;
      this.w = w;
    }

    protected final int h;
    protected final int w;

    protected List<GridPoint> vertexes;

    @Override
    public Collection<GridPoint> getVertexes() {

      // lazy load
      if (vertexes == null) {
        vertexes = new ArrayList<>(w * h);
        for (int r = 0; r < h; r++) {
          for (int c = 0; c < w; c++) {
            vertexes.add(new GridPoint(r, c));
          }
        }
      }

      return vertexes;
    }
  }

  private static interface Encoded<E extends Encoded> {

  }

  private static interface Encoder<E extends Encoded> {

    int num();

    Integer encode(E from);

    E decode(Integer from);
  }

  private static abstract class EncodedGraph<V extends Encoded<V>> implements Graph<V> {

    public EncodedGraph(Encoder<V> encoder) {
      this.encoder = encoder;
      this.delegate = new IntVertexGraph() {
        @Override
        public Collection<Integer> getVertexes() {
          return EncodedGraph.this.getVertexes().stream()
              .map(encoder::encode)
              .collect(Collectors.toList());
        }

        @Override
        public int getSize() {
          return EncodedGraph.this.getSize();
        }

        @Override
        public Collection<Edge<Integer>> getEdges(Integer from) {
          Collection<Edge<V>> edges = EncodedGraph.this.getEdges(encoder.decode(from));
          List<Edge<Integer>> ans = new ArrayList<>(edges.size());
          for (Edge<V> e : edges) {
            ans.add(new Edge<>(
                encoder.encode(e.getFrom()), encoder.encode(e.getTo()), e.getWeight()));
          }
          return ans;
        }
      };
    }

    final Encoder<V> encoder;
    final IntVertexGraph delegate;

    public PathQuery<V> pathQuery(Function<Graph<Integer>, PathQuery<Integer>> delegate) {
      return new EncodedPathQuery<>(delegate, this);
    }

    public MultiPathQuery<V> multiPathQuery(
        Function<Graph<Integer>, MultiPathQuery<Integer>> delegate) {
      return new EncodedMultiPathQuery<>(delegate, this);
    }

    private static class EncodedPathQuery<V extends Encoded<V>> implements PathQuery<V> {

      EncodedGraph<V> graph;
      PathQuery<Integer> delegate;

      private EncodedPathQuery(Function<Graph<Integer>, PathQuery<Integer>> delegate,
          EncodedGraph<V> graph) {
        this.graph = graph;
        this.delegate = delegate.apply(graph.delegate);
      }

      @Override
      public VertexPath<V> path(V begin, V end) {
        VertexPath<Integer> path = delegate
            .path(graph.encoder.encode(begin), graph.encoder.encode(end));
        if (path == null) {
          return null;
        }
        return new EfficientVertexPath<>(graph.encoder.decode(path.getBegin()),
            graph.encoder.decode(path.getEnd()), path.getWeight());
      }
    }

    private static class EncodedMultiPathQuery<V extends Encoded<V>> implements MultiPathQuery<V> {

      EncodedGraph<V> graph;
      MultiPathQuery<Integer> delegate;

      private EncodedMultiPathQuery(Function<Graph<Integer>, MultiPathQuery<Integer>> delegate,
          EncodedGraph<V> graph) {
        this.graph = graph;
        this.delegate = delegate.apply(graph.delegate);
      }

      @Override
      public VertexPath<V> path(List<V> begin, V end) {
        List<Integer> list = begin.stream().map(graph.encoder::encode)
            .collect(Collectors.toList());
        VertexPath<Integer> path = delegate
            .path(list, graph.encoder.encode(end));
        if (path == null) {
          return null;
        }
        return new EfficientVertexPath<>(graph.encoder.decode(path.getBegin()),
            graph.encoder.decode(path.getEnd()), path.getWeight());
      }
    }

  }

  private static interface PathQuery<V> {

    Graph.VertexPath<V> path(V begin, V end);
  }

  private static interface MultiPathQuery<V> {

    Graph.VertexPath<V> path(List<V> begin, V end);
  }

  private static abstract class QueuedPathQuery<V> implements PathQuery<V> {

    protected final Graph<V> graph;
    protected final Queue<Graph.VertexPath<V>> queue;

    public QueuedPathQuery(Graph<V> graph, Queue<Graph.VertexPath<V>> queue) {
      this.graph = graph;
      this.queue = queue;
    }

    public Graph.VertexPath<V> path(V begin, V end) {

      prepare(begin, end);
      queue.add(new Graph.EfficientVertexPath<>(begin));
      return search(end);
    }

    protected Graph.VertexPath<V> search(V end) {
      while (!queue.isEmpty()) {
        Graph.VertexPath<V> path = queue.remove();
        V head = path.getEnd();

        if (head.equals(end)) {
          return path;
        }

        for (Graph.Edge<V> e : graph.getEdges(head)) {
          if (!predicate(e)) {
            continue;
          }
          Graph.VertexPath<V> p = path.append(e);
          queue.add(p);
          mark(path, e, p);
        }
      }
      return null;
    }

    abstract void prepare(V begin, V end);

    abstract boolean predicate(Graph.Edge<V> edge);

    abstract void mark(Graph.VertexPath<V> existing, Graph.Edge<V> edge,
        Graph.VertexPath<V> path);
  }

  private static class DijkstraPathQuery extends QueuedPathQuery<Integer> {

    private final long[] distance;

    public DijkstraPathQuery(Graph<Integer> graph) {
      super(graph, new PriorityQueue<>(
          Comparator.comparingLong(Graph.VertexPath::getWeight)));
      distance = new long[graph.getSize()];
    }

    @Override
    void prepare(Integer begin, Integer end) {
      for (Integer v : graph.getVertexes()) {
        distance[v] = Integer.MAX_VALUE;
      }
    }

    @Override
    boolean predicate(Graph.Edge<Integer> edge) {
      return distance[edge.getFrom()] + edge.getWeight() < distance[edge.getTo()];
    }

    @Override
    void mark(Graph.VertexPath<Integer> existing, Graph.Edge<Integer> edge,
        Graph.VertexPath<Integer> path) {
      distance[path.getEnd()] = path.getWeight();
    }
  }

  private static class BfsPathQuery extends QueuedPathQuery<Integer> implements
      MultiPathQuery<Integer> {

    final boolean[] visited;

    public BfsPathQuery(Graph<Integer> graph) {
      super(graph, new ArrayDeque<>());
      visited = new boolean[graph.getSize()];
    }

    @Override
    void prepare(Integer begin, Integer end) {
      visited[begin] = true;
    }

    @Override
    boolean predicate(Graph.Edge<Integer> edge) {
      return !visited[edge.getTo()];
    }

    @Override
    void mark(Graph.VertexPath<Integer> existing, Graph.Edge<Integer> edge,
        Graph.VertexPath<Integer> path) {
      visited[path.getEnd()] = true;
    }

    public Graph.VertexPath<Integer> path(List<Integer> begins, Integer end) {

      begins.forEach(begin -> prepare(begin, end));
      begins.forEach(begin -> queue.add(new Graph.EfficientVertexPath<>(begin)));
      return search(end);
    }

  }

  private static abstract class MemoDfsPathQuery<V> extends DfsPathQuery<V> {

    public MemoDfsPathQuery(Graph<V> graph) {
      super(graph);
    }

    abstract Graph.VertexPath<V> memo(Graph.VertexPath<V> path, V parent,
        V end);

    @Override
    public Graph.VertexPath<V> path(Graph.VertexPath<V> path, V parent,
        V end) {
      Graph.VertexPath<V> memo = memo(path, parent, end);
      if (memo != null) {
        return memo;
      }
      return super.path(path, parent, end);
    }
  }

  private static class DfsPathQuery<V> implements PathQuery<V> {

    protected final Graph<V> graph;

    public DfsPathQuery(Graph<V> graph) {
      this.graph = graph;
    }

    @Override
    public Graph.VertexPath<V> path(V begin, V end) {
      return path(new Graph.EfficientVertexPath<>(begin), null, end);
    }

    protected Graph.VertexPath<V> path(Graph.VertexPath<V> path, V parent,
        V end) {
      V head = path.getEnd();
      if (path.getEnd().equals(end)) {
        return path;
      }
      for (Graph.Edge<V> e : graph.getEdges(head)) {
        if (!e.getTo().equals(parent)) {
          Graph.VertexPath<V> next = path(path.append(e), head, end);
          if (next != null) {
            return next;
          }
        }
      }
      return null;
    }
  }

  private static class WarshallFloydQuery implements PathQuery<Integer> {

    private Graph.VertexPath<Integer>[][] shortest;

    private WarshallFloydQuery(Graph<Integer> graph) {
      Collection<Integer> nodes = graph.getVertexes();

      shortest = new Graph.VertexPath[nodes.size()][nodes.size()];

      for (int from : nodes) {
        Collection<Graph.Edge<Integer>> edges = graph.getEdges(from);
        for (Graph.Edge<Integer> e : edges) {
          shortest[e.getFrom()][e.getTo()] = new Graph.EfficientVertexPath<>(e);
        }
        shortest[from][from] = new Graph.EfficientVertexPath<>(from);
      }

      for (int relay : nodes) {
        for (int from : nodes) {
          for (int dest : nodes) {
            Graph.VertexPath<Integer> pathA = shortest[from][relay];
            Graph.VertexPath<Integer> pathB = shortest[relay][dest];
            if (pathA != null && pathB != null) {
              if (shortest[from][dest] == null
                  || pathA.getWeight() + pathB.getWeight() < shortest[from][dest]
                  .getWeight()) {
                shortest[from][dest] = pathA.append(pathB);
              }
            }
          }
        }
      }
    }

    @Override
    public Graph.VertexPath<Integer> path(Integer begin, Integer end) {
      return shortest[begin][end];
    }
  }

  private static abstract class IntVertexGraph implements Graph<Integer> {

  }

  private static interface Graph<V> {

    static class Edge<V> {

      public Edge(V from, V to, long weight) {
        this.from = from;
        this.to = to;
        this.weight = weight;
      }

      private final V from;

      private final V to;
      private final long weight;

      V getFrom() {
        return from;
      }

      V getTo() {
        return to;
      }

      long getWeight() {
        return weight;
      }

      @Override
      public boolean equals(Object o) {
        if (this == o) {
          return true;
        }
        if (o == null || getClass() != o.getClass()) {
          return false;
        }
        Edge<?> edge = (Edge<?>) o;
        return weight == edge.weight &&
            Objects.equals(from, edge.from) &&
            Objects.equals(to, edge.to);
      }

      @Override
      public int hashCode() {
        return Objects.hash(from, to, weight);
      }
    }

    static class ConstantWeightEdge<V> extends Edge<V> {

      public ConstantWeightEdge(V from, V to) {
        super(from, to, 1);
      }
    }

    Collection<V> getVertexes();

    int getSize();

    Collection<Edge<V>> getEdges(V from);

    static interface VertexPath<V> {

      V getBegin();

      V getEnd();

      long getWeight();

      VertexPath<V> append(VertexPath<V> other);

      VertexPath<V> append(Edge<V> edge);
    }

    static class EfficientVertexPath<V> implements VertexPath<V> {

      final V begin;
      final V end;
      final long weight;

      public EfficientVertexPath(V begin) {
        this.begin = begin;
        this.end = begin;
        this.weight = 0;
      }

      public EfficientVertexPath(V begin, V end, long weight) {
        this.begin = begin;
        this.end = end;
        this.weight = weight;
      }

      public EfficientVertexPath(Edge<V> edge) {
        this.begin = edge.getFrom();
        this.end = edge.getTo();
        this.weight = edge.getWeight();
      }

      private EfficientVertexPath(VertexPath<V> path, Edge<V> append) {
        this.begin = path.getBegin();
        if (!path.getEnd().equals(append.getFrom())) {
          throw new IllegalStateException("not correct edge.");
        }
        this.end = append.getTo();
        this.weight = path.getWeight() + append.getWeight();
      }

      private EfficientVertexPath(VertexPath<V> pathA, VertexPath<V> pathB) {
        this.begin = pathA.getBegin();
        if (!pathA.getEnd().equals(pathB.getBegin())) {
          throw new IllegalStateException("not correct edge.");
        }
        this.end = pathB.getEnd();
        this.weight = pathA.getWeight() + pathB.getWeight();
      }

      @Override
      public V getBegin() {
        return begin;
      }

      @Override
      public V getEnd() {
        return end;
      }

      @Override
      public long getWeight() {
        return weight;
      }

      @Override
      public VertexPath<V> append(VertexPath<V> other) {
        return new EfficientVertexPath<>(this, other);
      }

      @Override
      public VertexPath<V> append(Edge<V> edge) {
        return new EfficientVertexPath<>(this, edge);
      }

      @Override
      public boolean equals(Object o) {
        if (this == o) {
          return true;
        }
        if (o == null || getClass() != o.getClass()) {
          return false;
        }
        EfficientVertexPath<?> that = (EfficientVertexPath<?>) o;
        return weight == that.weight &&
            Objects.equals(begin, that.begin) &&
            Objects.equals(end, that.end);
      }

      @Override
      public int hashCode() {
        return Objects.hash(begin, end, weight);
      }
    }
  }

  private static class Scanner {

    private final InputStream is;
    private final byte[] buffer = new byte[1024];
    private int ptr = 0;
    private int buflen = 0;

    Scanner(InputStream is) {
      this.is = is;
    }

    private boolean hasNextByte() {
      if (ptr < buflen) {
        return true;
      } else {
        ptr = 0;
        try {
          buflen = is.read(buffer);
        } catch (IOException e) {
          e.printStackTrace();
        }
        if (buflen <= 0) {
          return false;
        }
      }
      return true;
    }

    private int readByte() {
      if (hasNextByte()) {
        return buffer[ptr++];
      } else {
        return -1;
      }
    }

    private static boolean isPrintableChar(int c) {
      return 33 <= c && c <= 126;
    }

    public boolean hasNext() {
      while (hasNextByte() && !isPrintableChar(buffer[ptr])) {
        ptr++;
      }
      return hasNextByte();
    }

    public String next() {
      if (!hasNext()) {
        throw new NoSuchElementException();
      }
      StringBuilder sb = new StringBuilder();
      int b = readByte();
      while (isPrintableChar(b)) {
        sb.appendCodePoint(b);
        b = readByte();
      }
      return sb.toString();
    }

    public long nextLong() {
      if (!hasNext()) {
        throw new NoSuchElementException();
      }
      long n = 0;
      boolean minus = false;
      int b = readByte();
      if (b == '-') {
        minus = true;
        b = readByte();
      }
      if (b < '0' || '9' < b) {
        throw new NumberFormatException();
      }
      while (true) {
        if ('0' <= b && b <= '9') {
          n *= 10;
          n += b - '0';
        } else if (b == -1 || !isPrintableChar(b)) {
          return minus ? -n : n;
        } else {
          throw new NumberFormatException();
        }
        b = readByte();
      }
    }

    public int nextInt() {
      long nl = nextLong();
      if (nl < Integer.MIN_VALUE || nl > Integer.MAX_VALUE) {
        throw new NumberFormatException();
      }
      return (int) nl;
    }

    public double nextDouble() {
      return Double.parseDouble(next());
    }
  }
}


import java.util.ArrayDeque;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Queue;
import java.util.Scanner;
import java.util.Set;

public class Main {
    static final Scanner sc  = new Scanner(System.in);
    static final int MOD = (int) 1E9 + 7;
    static final int INF = Integer.MAX_VALUE;
    static final char BLACK = '#';
    static final char WHITE = '.';

    public static void main(String[] args) {
        int H = nint();
        int W = nint();
        char[][] A = nchars2(H, W);

        int countB = 0;
        Queue<Point> q = new ArrayDeque<>();
        for (int h = 0; h < H; h++) {
            for (int w = 0; w < W; w++) {
                if (A[h][w] == BLACK) {
                    countB++;
                    q.add(new Point(h, w));
                }
            }
        }

        Point minP = new Point(0, 0);
        Point maxP = new Point(H-1, W-1);
        int ans = 0;

        while (countB < H*W) {
            Set<Point> s = new HashSet<>();
            while (!q.isEmpty()) {
                s.add(q.poll());
            }

            for (Point p: s) {
                for (int i = 0; i < Point.mv4.length; i++) {
                    Point next = p.add(Point.mv4[i]);
                    if (next.isIn(minP, maxP) && A[next.h][next.w] == WHITE) {
                        q.add(next);
                        countB++;
                        A[next.h][next.w] = BLACK;
                    }
                }
            }
            ans++;
        }

        System.out.println(ans);
    }

    public static class Point {
        public int h;
        public int w;
        public Point(int h, int w) {
            this.h = h;
            this.w = w;
        }

        public boolean isIn(int minH, int maxH, int minW, int maxW) {
            return minH <= this.h && this.h <= maxH && minW <= this.w && this.w <= maxW;
        }

        public boolean isIn(Point min, Point max) {
            return isIn(min.h, max.h, min.w, max.w);
        }

        public Point add(Point mv) {
            return new Point(this.h + mv.h, this.w + mv.w);
        }

        @Override
        public boolean equals(Object obj) {
            Point that = (Point) obj;
            return this.h == that.h && this.w == that.w;
        }

        @Override
        public String toString() {
            return "(" + h + ", " + w + ")";
        }

        public static final Point[] mv8 = {
                new Point(1, 0), new Point( 0, 1), new Point(-1,  0), new Point(0, -1),
                new Point(1, 1), new Point(-1, 1), new Point(-1, -1), new Point(1, -1)};

        public static final Point[] mv4 = {
                new Point(1, 0), new Point( 0, 1), new Point(-1,  0), new Point(0, -1)};
    }

    private static int nint() {
        return sc.nextInt();
    }

    private static int[] nints(int n) {
        return nints(n, 0, 0);
    }

    private static int[] nints(int n, int padL, int padR) {
        int[] a = new int[padL + n + padR];
        for (int i = 0; i < n; i++)
            a[padL + i] = nint();
        return a;
    }

    private static long nlong() {
        return sc.nextLong();
    }

    private static long[] nlongs(int n) {
        return nlongs(n, 0, 0);
    }

    private static long[] nlongs(int n, int padL, int padR) {
        long[] a = new long[padL + n + padR];
        for (int i = 0; i < n; i++)
            a[padL + i] = nlong();
        return a;
    }

    private static double ndouble() {
        return sc.nextDouble();
    }

    private static double[] ndoubles(int n) {
        return ndoubles(n, 0, 0);
    }

    private static double[] ndoubles(int n, int padL, int padR) {
        double[] d = new double[n + padL + padR];
        for (int i = 0; i < n; i++) {
            d[padL + i] = ndouble();
        }
        return d;
    }

    private static String nstr() {
        return sc.next();
    }

    private static char[] nchars() {
        return sc.next().toCharArray();
    }

    private static char[] nchars(int padL, int padR) {
        char[] temp = sc.next().toCharArray();
        char[] ret = new char[temp.length + padL + padR];
        System.arraycopy(temp, 0, ret, padL, temp.length);
        return ret;
    }

    private static char[][] nchars2(int h, int w) {
        return nchars2(h, w, 0, 0);
    }

    private static char[][] nchars2(int h, int w, int padLU, int padRD) {
        char[][] a2 = new char[h + padLU + padRD][w + padLU + padRD];
        for (int i = 0; i < h; i++)
            System.arraycopy(nchars(), 0, a2[padLU + i], padLU, w);
        return a2;
    }

    private static long min(long... ls) {
        return Arrays.stream(ls).min().getAsLong();
    }

    private static long max(long... ls) {
        return Arrays.stream(ls).max().getAsLong();
    }
}