Problem:
Score : 400 points

Problem Statement

There is a grid with H horizontal rows and W vertical columns, and there are obstacles on some of the squares.

Snuke is going to choose one of the squares not occupied by an obstacle and place a lamp on it.
The lamp placed on the square will emit straight beams of light in four cardinal directions: up, down, left, and right.
In each direction, the beam will continue traveling until it hits a square occupied by an obstacle or it hits the border of the grid. It will light all the squares on the way, including the square on which the lamp is placed, but not the square occupied by an obstacle.

Snuke wants to maximize the number of squares lighted by the lamp.

You are given H strings S_i (1 \leq i \leq H), each of length W. If the j-th character (1 \leq j \leq W) of S_i is #, there is an obstacle on the square at the i-th row from the top and the j-th column from the left; if that character is ., there is no obstacle on that square.

Find the maximum possible number of squares lighted by the lamp.

Constraints

1 \leq H \leq 2,000

1 \leq W \leq 2,000

S_i is a string of length W consisting of # and ..

. occurs at least once in one of the strings S_i (1 \leq i \leq H).

Input

Input is given from Standard Input in the following format:

H W
S_1
:
S_H

Output

Print the maximum possible number of squares lighted by the lamp.

Sample Input 1

4 6
#..#..
.....#
....#.
#.#...

Sample Output 1

8

If Snuke places the lamp on the square at the second row from the top and the second column from the left, it will light the following squares: the first through fifth squares from the left in the second row, and the first through fourth squares from the top in the second column, for a total of eight squares.

Sample Input 2

8 8
..#...#.
....#...
##......
..###..#
...#..#.
##....#.
#...#...
###.#..#

Sample Output 2

13

Solution in C# (Mono 4.6.2.0) (judge verdict: Accepted)

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using static Ha2ne2Util.Cin;
using static Ha2ne2Util.Util;
using static System.Console;
using static System.Math;
using System.Collections;
using Pair = System.Tuple<long, long>;

/// <summary>
/// https://atcoder.jp/contests/abc129
/// 2020/02/04 R1305
/// </summary>
namespace ABC129
{
    public class Program
    {
        public static void Main(string[] args)
        {
            // 1回目
            // A();  2m
            // B();  7m
            // C(); 19m
            // D(); 54m
            // E();
            // F();

            // 2回目
            // A();  2m
            // B();  6m
            // C(); 13m
            D();
            // E();
            // F();
        }

        public static void F()
        {
        }
        public static void E()
        {
        }
        public static void D()
        {
            long H = rl;
            long W = rl;
            string[] table = new string[H];
            for (int i = 0; i < H; i++)
            {
                table[i] = rs;
            }

            long[,] top = new long[H,W];
            long[,] bottom = new long[H,W];
            long[,] left = new long[H,W];
            long[,] right = new long[H,W];

            for (int w = 0; w < W; w++)
            {
                top[0, w] = table[0][w] == '#' ? 0 : 1;
                for (int h = 1; h < H; h++)
                {
                    if (table[h][w] == '#')
                        continue;
                    top[h, w] = top[h - 1, w] + 1;
                }

                bottom[H-1, w] = table[H-1][w] == '#' ? 0 : 1;
                for (int h = (int)H - 2; h > -0; h--)
                {
                    if (table[h][w] == '#')
                        continue;
                    bottom[h, w] = bottom[h + 1, w] + 1;
                }
            }

            for (int h = 0; h < H; h++)
            {
                left[h, 0] = table[h][0] == '#' ? 0 : 1;
                for (int w = 1; w < W; w++)
                {
                    if (table[h][w] == '#')
                        continue;
                    left[h, w] = left[h, w - 1] + 1;
                }

                right[h, W-1] = table[h][(int)W-1] == '#' ? 0 : 1;
                for (int w = (int)W - 2; w >= 0; w--)
                {
                    if (table[h][w] == '#')
                        continue;
                    right[h, w] = right[h, w + 1] + 1;
                }
            }

            long ans = -INF;
            for (int h = 0; h < H; h++)
            {
                for (int w = 0; w < W; w++)
                {
                    long res = right[h, w] + left[h, w] + top[h, w] + bottom[h, w] - 3;
                    ChMax(ref ans, res);
                }
            }

            Console.WriteLine(ans);
        }

        public static void C()
        {
            long N = rl;
            long M = rl;
            bool[] ng = new bool[N];
            for (int i = 0; i < M; i++)
            {
                ng[rl] = true;
            }

            long[] dp = new long[N + 2];
            dp[0] = 1;
            for (int i = 0; i < N; i++)
            {
                if (ng[i])
                    continue;
                ChModAdd(ref dp[i + 1], dp[i]);
                ChModAdd(ref dp[i + 2], dp[i]);
            }

            Console.WriteLine(dp[N]);
        }

        public static void B()
        {
            long N = rl;
            long[] A = rla;
            long[] S = new long[N+1];
            for (int i = 0; i < N; i++)
            {
                S[i + 1] = S[i] + A[i];
            }

            long min = INF;
            for (int i = 1; i < N; i++)
            {
                ChMin(ref min, Abs(S[i] * 2 - S[N]));
            }

            Console.WriteLine(min);
        }
        public static void A()
        {
            long[] A = rla;
            Sort(A);

            Console.WriteLine(A[0] + A[1]);
        }

        //public static void F()
        //{

        //}

        //public static void E()
        //{
        //    string S = rs;
        //    long N = S.Length;
        //    long[,] dp = new long[N + 1, 2];
        //    dp[0, 1] = 1;
        //    for (int i = 0; i < N; i++)
        //    {
        //        dp[i + 1, 0] = ModMul(dp[i, 0], 3);

        //        if (S[i] == '1')
        //        {
        //            dp[i + 1, 1] = ModAdd(
        //                dp[i + 1, 1],
        //                ModMul(dp[i, 1], 2));
        //            dp[i + 1, 0] = ModAdd(
        //                dp[i + 1, 0],
        //                dp[i, 1]);
        //        }
        //        else
        //        {
        //            dp[i + 1, 1] = dp[i, 1];
        //        }
        //    }

        //    Console.WriteLine(ModAdd(dp[N, 0], dp[N, 1]));
        //}

        //public static void D()
        //{
        //    long H = rl;
        //    long W = rl;
        //    string[] A = new string[H];
        //    for (int i = 0; i < H; i++)
        //    {
        //        A[i] = ReadString();
        //    }

        //    // 横方向の下準備
        //    long[,] yoko = new long[H, W];
        //    for (int i = 0; i < H; i++)
        //    {
        //        string s = A[i];
        //        for (int j = 0; j < W; j++)
        //        {
        //            int count = 0;
        //            while (j + count < W && s[j + count] != '#')
        //            {
        //                count++;
        //            }
        //            for (int k = j; k < j + count; k++)
        //            {
        //                yoko[i, k] = count;
        //            }

        //            j = j + count;
        //        }
        //    }

        //    // 縦方向の下準備
        //    long[,] tate = new long[H, W];
        //    for (int w = 0; w < W; w++)
        //    {
        //        for (int h = 0; h < H; h++)
        //        {
        //            int count = 0;
        //            while (h + count < H && A[h + count][w] != '#')
        //            {
        //                count++;
        //            }
        //            for (int k = h; k < h + count; k++)
        //            {
        //                tate[k, w] = count;
        //            }

        //            h = h + count;
        //        }
        //    }

        //    long ans = 0;
        //    for (int h = 0; h < H; h++)
        //    {
        //        for (int w = 0; w < W; w++)
        //        {
        //            ChMax(ref ans,tate[h, w] + yoko[h, w] - 1);
        //        }
        //    }

        //    Console.WriteLine(ans);
        //}

        //public static void C()
        //{
        //    long N = rl;
        //    long M = rl;
        //    bool[] A = new bool[N];
        //    for (int i = 0; i < M; i++)
        //    {
        //        A[rl] = true;
        //    }

        //    long[] dp = new long[N + 1];
        //    dp[0] = 1;
        //    for (int i = 0; i < N; i++)
        //    {
        //        if (A[i] == true)
        //            continue;
        //        dp[i + 1] = ModAdd(dp[i + 1], dp[i]);
        //        if (i + 2 <= N)
        //            dp[i + 2] = dp[i];
        //    }

        //    Console.WriteLine(dp[N]);
        //}

        //public static void B()
        //{
        //    long N = rl;
        //    long[] A = rla;
        //    long[] S = new long[N + 1];
        //    for (int i = 0; i < N; i++)
        //    {
        //        S[i + 1] = S[i] + A[i];
        //    }
        //    long ans = INF;

        //    for (int i = 1; i < N; i++)
        //    {
        //        ChMin(ref ans, Abs(S[i] * 2 - S[N]));
        //    }

        //    Console.WriteLine(ans);
        //}

        //public static void A()
        //{
        //    long[] A = rla;
        //    Sort(A);
        //    Console.WriteLine(A.Take(2).Sum());
        //}
    }
}

namespace Ha2ne2Util
{
    public static class Cin
    {
        private static string[] Tokens { get; set; }

        private static int Pointer { get; set; }

        public static string Next()
        {
            if (Tokens == null || Tokens.Length <= Pointer)
            {
                Tokens = Console.ReadLine().Split(' ');
                Pointer = 0;
            }

            return Tokens[Pointer++];
        }

        public static int ReadInt()
        {
            return int.Parse(Next());
        }

        public static long rl => ReadLong();
        public static long[] rla => ReadLongArray();
        public static double rd => ReadDouble();
        public static double[] rda => ReadDoubleArray();
        public static string rs => ReadString();

        public static long ReadLong()
        {
            return long.Parse(Next());
        }

        public static double ReadDouble()
        {
            return double.Parse(Next());
        }

        public static int[] ReadIntArray()
        {
            Tokens = null;
            return Array.ConvertAll(Console.ReadLine().Split(' '), int.Parse);
        }

        public static long[] ReadLongArray()
        {
            Tokens = null;
            return Array.ConvertAll(Console.ReadLine().Split(' '), long.Parse);
        }

        public static double[] ReadDoubleArray()
        {
            Tokens = null;
            return Array.ConvertAll(Console.ReadLine().Split(' '), double.Parse);
        }

        public static void ReadArrays(out long[] a, out long[] b, long N)
        {
            a = new long[N];
            b = new long[N];
            for (int i = 0; i < N; i++)
            {
                a[i] = ReadLong();
                b[i] = ReadLong();
            }
        }

        public static void ReadArrays(out long[] a, out long[] b, out long[] c, long N)
        {
            a = new long[N];
            b = new long[N];
            c = new long[N];
            for (int i = 0; i < N; i++)
            {
                a[i] = ReadLong();
                b[i] = ReadLong();
                c[i] = ReadLong();
            }
        }

        public static int[][] ReadIntTable(int n)
        {
            Tokens = null;
            int[][] ret = new int[n][];
            for (int i = 0; i < n; i++)
            {
                ret[i] = ReadIntArray();
            }

            return ret;
        }

        public static long[][] ReadLongTable(long n)
        {
            Tokens = null;
            long[][] ret = new long[n][];
            for (long i = 0; i < n; i++)
            {
                ret[i] = ReadLongArray();
            }

            return ret;
        }

        public static string ReadString()
        {
            return Next();
        }

        /// <summary>
        /// "00101001010"という様な入力をintの配列にして返す
        /// </summary>
        /// <returns></returns>
        public static int[] ReadIntArrayFromBinaryString()
        {
            Tokens = null;
            return Array.ConvertAll(Console.ReadLine().ToArray(), c => int.Parse(c.ToString()));
        }

        /// <summary>
        /// "00101001010"という様な入力をboolの配列にして返す
        /// </summary>
        /// <returns></returns>
        public static bool[] ReadBoolArrayFromBinaryString()
        {
            Tokens = null;
            return Console.ReadLine().Select(c => c == '1').ToArray();
        }
    }

    /// <summary>
    /// ユーティリティー
    /// </summary>
    public static class Util
    {
        // 10^17
        public static readonly long INF = (long)1e17;

        public static IEnumerable<int> Range(int end)
        {
            for (int i = 0; i < end; i++)
            {
                yield return i;
            }
        }

        public static IEnumerable<int> Range(int from, int end)
        {
            for (int i = from; i < end; i++)
            {
                yield return i;
            }
        }

        public static IEnumerable<int> Range(int from, int end, int step)
        {
            for (int i = from; i < end; i += step)
            {
                yield return i;
            }
        }

        /// <summary>
        /// ソートをして結果を返します。
        /// 破壊的関数です。
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="array"></param>
        /// <returns></returns>
        public static T[] Sort<T>(T[] array)
        {
            Array.Sort<T>(array);
            return array;
        }

        /// <summary>
        /// 降順ソートをして結果を返します。
        /// 破壊的関数です。
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="array"></param>
        /// <returns></returns>
        public static T[] SortDecend<T>(T[] array)
        {
            Array.Sort<T>(array);
            Array.Reverse(array);
            return array;
        }

        /// <summary>
        /// Rubyにあるようなやつ
        /// ex) 5.Times(i => Console.WriteLine(i));
        /// </summary>
        /// <param name="times"></param>
        /// <param name="action"></param>
        public static void Times(this int times, Action<int> action)
        {
            for (int i = 0; i < times; i++)
            {
                action(i);
            }
        }

        /// <summary>
        /// 引数aと引数bの値を入れ替えます
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="a"></param>
        /// <param name="b"></param>
        public static void Swap<T>(ref T a, ref T b)
        {
            T _a = a;
            a = b;
            b = _a;
        }

        /// <summary>
        /// Console.WriteLineの自動フラッシュをしないようにする
        /// </summary>
        public static void DontAutoFlush()
        {
            var sw = new StreamWriter(Console.OpenStandardOutput()) { AutoFlush = false };
            Console.SetOut(sw);
        }

        public static void Flush()
        {
            Console.Out.Flush();
        }

        /// <summary>
        /// 先頭と末尾に要素を増やした新しい配列を返します。
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="array"></param>
        /// <returns></returns>
        public static T[] AddHeadAndTail<T>(T[] array)
        {
            int len = array.Length;
            T[] res = new T[len + 2];
            Array.Copy(array, 0, res, 1, len);
            return res;
        }

        /// <summary>
        /// 昇順ソート済みの配列を2分探索します。
        /// 要素が見つからなかった場合はnより大きい数値の中で最小の数値のインデックスを返す。
        /// </summary>
        /// <param name="array"></param>
        /// <param name="n"></param>
        /// <returns></returns>
        public static int BinarySearch(long[] array, long n)
        {
            int left = 0;
            int right = array.Length - 1;

            while (left <= right)
            {
                int mid = (right - left) / 2 + left;

                if (array[mid] == n)
                {
                    return mid;
                }
                else if (n < array[mid])
                {
                    right = mid - 1;
                }
                else
                {
                    left = mid + 1;
                }
            }

            return left;
        }

        public static int GCD(int a, int b)
        {
            return (b == 0) ? a : GCD(b, a % b);
        }

        public static int[] GetDivisor(int n)
        {
            double sqrt = Math.Sqrt(n);

            List<int> divisor = new List<int>();

            for (int i = 1; i <= sqrt; i++)
            {
                if (n % i == 0)
                {
                    divisor.Add(i);
                    int tmp = n / i;
                    if (tmp != i)
                    {
                        divisor.Add(tmp);
                    }
                }
            }

            return Sort(divisor.ToArray());
        }

        public static int[] LongToBinaryArray(long n)
        {
            List<int> lst = new List<int>();

            while (n > 0)
            {
                int amari = (int)(n % 2);
                lst.Add(amari);
                n /= 2;
            }

            return lst.ToArray();
        }

        public static long BinaryArrayToLong(int[] binaryArray)
        {
            long result = 0;
            long b = 1;
            int len = binaryArray.Length;

            for (int i = 0; i < len; i++)
            {
                result += binaryArray[i] * b;
                b *= 2;
            }

            return result;
        }

        public static void Deconstruct<T>(this T[] items, out T t0)
        {
            t0 = items.Length > 0 ? items[0] : default(T);
        }

        public static void Deconstruct<T>(this T[] items, out T t0, out T t1)
        {
            if (items.Length < 2)
                throw new ArgumentException();

            t0 = items[0];
            t1 = items[1];
        }

        public static void Deconstruct<T>(this T[] items, out T t0, out T t1, out T t2)
        {
            if (items.Length < 3)
                throw new ArgumentException();

            t0 = items[0];
            t1 = items[1];
            t2 = items[2];
        }

        public static void Deconstruct<T>(this T[] items, out T t0, out T t1, out T t2, out T t3)
        {
            if (items.Length < 4)
                throw new ArgumentException();

            t0 = items[0];
            t1 = items[1];
            t2 = items[2];
            t3 = items[3];
        }

        public static void ArrayInitialize<T>(T[,] array, T value)
        {
            int len0 = array.GetLength(0);
            int len1 = array.GetLength(1);

            for (int i = 0; i < len0; i++)
            {
                for (int j = 0; j < len1; j++)
                {
                    array[i, j] = value;
                }
            }
        }

        public static void ChMax(ref long a, long b)
        {
            if (a < b) a = b;
        }

        public static void ChMin(ref long a, long b)
        {
            if (a > b) a = b;
        }

        public readonly static int MOD = 1000000007;
        public static long ModAdd(long a, long b)
        {
            long res = a + b;
            if (res >= MOD)
                return res % MOD;
            return res;
        }
        public static long ModMul(long a, long b)
        {
            long res = a * b;
            if (res >= MOD)
                return res % MOD;
            return res;
        }

        public static void ChModAdd(ref long a, long b)
        {
            a += b;
            if (a >= MOD)
                a %= MOD;
        }

        public static void ChModMul(ref long a, long b)
        {
            a *= b;
            if (a >= MOD)
                a %= MOD;
        }

        public static T MinBy<T>(this IEnumerable<T> source, Func<T, long> conv)
        {
            T min = source.First();
            long minValue = conv(min);
            foreach (T x in source.Skip(1))
            {
                long n = conv(x);
                if (n < minValue)
                {
                    min = x;
                }
            }

            return min;
        }

        public static T MaxBy<T>(this IEnumerable<T> source, Func<T, long> conv)
        {
            T min = source.First();
            long maxValue = conv(min);
            foreach (T x in source.Skip(1))
            {
                long n = conv(x);
                if (maxValue < n)
                {
                    min = x;
                }
            }

            return min;
        }

        public static void FillArray<T>(T[] array, T value)
        {
            int max = array.Length;
            for (int i = 0; i < max; i++)
            {
                array[i] = value;
            }
        }

        public static void FillArray<T>(T[,] array, T value)
        {
            int max0 = array.GetLength(0);
            int max1 = array.GetLength(1);

            for (int i = 0; i < max0; i++)
            {
                for (int j = 0; j < max1; j++)
                {
                    array[i, j] = value;
                }
            }
        }

        public static void FillArray<T>(T[,,] array, T value)
        {
            int max0 = array.GetLength(0);
            int max1 = array.GetLength(1);
            int max2 = array.GetLength(2);

            for (int i = 0; i < max0; i++)
            {
                for (int j = 0; j < max1; j++)
                {
                    for (int k = 0; k < max2; k++)
                    {
                        array[i, j, k] = value;
                    }
                }
            }
        }

    }

    /// <summary>
    /// HashSetにTupleを入れた時の、等値性判定方法の指定に使います。
    /// HashSetのコンストラクタに渡して使います。
    /// EqualsとGetHashCodeを提供します。
    /// </summary>
    /// <typeparam name="T"></typeparam>
    public class EqualityComparer<T> : IEqualityComparer<T>
    {
        private Func<T, T, bool> _equals;
        private Func<T, int> _getHashCode;

        public EqualityComparer(Func<T, T, bool> equals, Func<T, int> getHashCode)
        {
            _equals = equals;
            _getHashCode = getHashCode;
        }

        public bool Equals(T x, T y)
        {
            return _equals(x, y);
        }

        public int GetHashCode(T obj)
        {
            return _getHashCode(obj);
        }
    }

    /// <summary>
    /// UnionFindTree
    /// </summary>
    public class UnionFindTree
    {
        public int[] Node;

        public UnionFindTree(int N)
        {
            Node = new int[N];
            for (int i = 0; i < N; i++)
            {
                Node[i] = -1;
            }
        }

        public bool IsSameGroup(int x, int y)
        {
            return GetRoot(x) == GetRoot(y);
        }

        public bool Merge(int x, int y)
        {
            int xr = GetRoot(x);
            int yr = GetRoot(y);

            if (xr == yr)
                return false;

            // xが、大きなグループを示すようにSwapする（値が小さい方が大きなグループ）
            if (Node[xr] > Node[yr])
                Swap(ref xr, ref yr);

            // グループの数を合算する
            Node[xr] += Node[yr];

            // 根を張り替える
            Node[yr] = xr;
            return true;
        }

        public int Size(int x)
        {
            return -Node[GetRoot(x)];
        }

        public int GetRoot(int n)
        {
            if (Node[n] < 0)
            {
                return n;
            }
            else
            {
                // 根を張りなおす。
                Node[n] = GetRoot(Node[n]);
                return Node[n];
            }
        }
    }

    /// <summary>
    /// PriorityQueue
    /// </summary>
    /// <typeparam name="T"></typeparam>
    public class PriorityQueue<T>
    {
        private Comparison<T> _comparison = null;
        private int _type = 0;

        private T[] _heap;
        private int _sz = 0;

        private int _count = 0;

        /// <summary>
        /// Priority queue
        /// </summary>
        /// <param name="maxSize">max size</param>
        /// <param name="type">0: asc, 1:desc</param>
        public PriorityQueue(int maxSize, int type = 0)
        {
            _heap = new T[maxSize];
            _type = type;
        }

        public PriorityQueue(int maxSize, Comparison<T> comparison)
        {
            _heap = new T[maxSize];
            _comparison = comparison;
        }

        private int Compare(T x, T y)
        {
            return _comparison(x, y);
            //if (_comparison != null) return _comparison(x, y);
            //return _type == 0 ? x.CompareTo(y) : y.CompareTo(x);
        }

        public void Push(T x)
        {
            _count++;

            //node number
            var i = _sz++;

            while (i > 0)
            {
                //parent node number
                var p = (i - 1) / 2;

                if (Compare(_heap[p], x) <= 0) break;

                _heap[i] = _heap[p];
                i = p;
            }

            _heap[i] = x;
        }

        public T Pop()
        {
            _count--;

            T ret = _heap[0];
            T x = _heap[--_sz];

            int i = 0;
            while (i * 2 + 1 < _sz)
            {
                //children
                int a = i * 2 + 1;
                int b = i * 2 + 2;

                if (b < _sz && Compare(_heap[b], _heap[a]) < 0) a = b;

                if (Compare(_heap[a], x) >= 0) break;

                _heap[i] = _heap[a];
                i = a;
            }

            _heap[i] = x;

            return ret;
        }

        public int Count()
        {
            return _count;
        }

        public T Peek()
        {
            return _heap[0];
        }

        public bool Contains(T x)
        {
            for (int i = 0; i < _sz; i++) if (x.Equals(_heap[i])) return true;
            return false;
        }

        public void Clear()
        {
            while (this.Count() > 0) this.Pop();
        }

        public IEnumerator<T> GetEnumerator()
        {
            var ret = new List<T>();

            while (this.Count() > 0)
            {
                ret.Add(this.Pop());
            }

            foreach (var r in ret)
            {
                this.Push(r);
                yield return r;
            }
        }

        public T[] ToArray()
        {
            T[] array = new T[_sz];
            int i = 0;

            foreach (var r in this)
            {
                array[i++] = r;
            }

            return array;
        }
    }
}