Problem:
J: Tiles are Colorful

ICPC で良い成績を収めるには修行が欠かせない．うさぎは ICPC で勝ちたいので，今日も修行をすることにした．

今日の修行は，流行りのパズルをすばやく解いて，瞬発力を鍛えようというものである．今日挑戦するのは，色とりどりのタイルが並んでいてそれらを上手く消していくパズルだ

初期状態では，グリッド上のいくつかのマスにタイルが置かれている．各タイルには色がついている．プレイヤーはゲーム開始後，以下の手順で示される操作を何回も行うことができる．

タイルが置かれていないマスを 1 つ選択し，そのマスを叩く．

叩いたマスから上に順に辿っていき，タイルが置かれているマスに至ったところでそのタイルに着目する．タイルが置かれているマスがないまま盤面の端に辿り着いたら何にも着目しない．

同様の操作を叩いたマスから下・左・右方向に対して行う．最大 4 枚のタイルが着目されることになる．

着目したタイルの中で同じ色のものがあれば，それらのタイルを盤面から取り除く．同じ色のタイルの組が 2 組あれば，それら両方を取り除く．

タイルを取り除いた枚数と同じ値の得点が入る．

着目をやめる．

たとえば，以下のような状況を考えよう．タイルが置かれていないマスはピリオドで，タイルの色はアルファベット大文字で表されている．

..A.......
.......B..
..........
..B.......
..A.CC....

ここで上から 2 行目，左から 3 列目のマスを叩く操作を考える．着目することになるタイルは A , B , B の 3 枚であるから，B の 2 枚が消えて盤面は以下のようになり，2 点を得る．

..A.......
..........
..........
..........
..A.CC....

このパズルはゆっくりしていると時間切れになってしまい，盤面の一部が見えなくなりどのくらい修行が足りなかったのかがわからなくなってしまう．
各色のタイルは 2 枚ずつ置かれているが，それらをすべて消せるとは限らないので，予めプログラムに得点の最大値を計算させておきたい．

Input

M N
C1,1C1,2...C1,N
C2,1C2,2...C2,N
...
CM,1CM,2...CM,N

整数 M, N は盤が 縦 M × 横 N のマス目であることを表す．Ci, j はアルファベット大文字またはピリオド ( . ) であり，上から i 行目，左から j 列目のマスについて，アルファベット大文字の場合は置かれているタイルの色を表し，ピリオドの場合はこのマスにタイルが置かれていないことを表す．

1 ≤ M ≤ 500，1 ≤ N ≤ 500 を満たす．各アルファベット大文字は入力中に 0 個または 2 個現れる．

Output

得点の最大値を 1 行に出力せよ．

Sample Input 1

5 10
..A.......
.......B..
..........
..B.......
..A.CC....

Sample Output 1

4

Sample Input 2

3 3
ABC
D.D
CBA

Sample Output 2

4

Sample Input 3

5 7
NUTUBOR
QT.SZRQ
SANAGIP
LMDGZBM
KLKIODP

Sample Output 3

34

Solution in Ruby (judge verdict: Accepted)

class Point
  attr_accessor :x, :y
  def initialize(i = 0, j = 0)
    @y = i
    @x = j
  end
  def ==(p)
    @y == p.y && @x == p.x
  end
  def +(add)
    Point.new(@y + add.y, @x + add.x)
  end
  def -(sub)
    Point.new(@y - sub.y, @x - sub.x)
  end
  def distance(p)
    (@y - p.y)**2 + (@x - p.x)**2
  end
end

def check(tile, p1, p2)
  add = Point.new
  add.y = ((p1.y < p2.y) ? 1 : -1) if p1.x == p2.x
  add.x = ((p1.x < p2.x) ? 1 : -1) if p1.y == p2.y
  now = p1.dup + add
  return false if now == p2
  while now != p2 do
    return false if tile[now.y][now.x] != "."
    now += add
  end
  return true
end

def delete!(tile, p1, p2)
  if p1.x == p2.x || p1.y == p2.y
    if check(tile, p1, p2)
      tile[p1.y][p1.x] = tile[p2.y][p2.x] = "."
      return true
    end
    return false
  end

  2.times do
    p1, p2 = p2, p1
    q = Point.new(p1.y, p2.x)
    next if tile[q.y][q.x] != "."
    next if q.distance(p1) != 1 && !check(tile, q, p1)
    next if q.distance(p2) != 1 && !check(tile, q, p2)
    tile[p1.y][p1.x] = tile[p2.y][p2.x] = "."
    return true
  end
  return false
end

H, W = gets.split.map(&:to_i)
tile = Array.new
H.times do
  tile.push(gets.chop.chars)
end

point = Hash.new {|h, k| h[k] = Array.new}
for i in 0...H do
  for j in 0...W do
    next if tile[i][j] == "."
    point[tile[i][j]].push(Point.new(i,j))
  end
end

answer = 0
26.times do
  point.each do |key, p|
    if delete!(tile, p[0], p[1])
      answer += 2
      point.delete(key)
    end
  end
end
p answer