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 JAVA (judge verdict: Accepted)

import java.util.*;
import java.lang.*;
import java.math.*;
import java.io.*;
import static java.lang.Math.*;
import static java.util.Arrays.*;

public class Main{
	Scanner sc=new Scanner(System.in);

	int INF=1<<28;
	double EPS=1e-12;

	int n, m;
	char[][] map;
	boolean[][] removed;
	int ans;
	int type='Z'-'A'+1;
	LinkedList<P> pList;

	void run(){
		n=sc.nextInt();
		m=sc.nextInt();
		map=new char[n][];
		for(int j=0; j<n; j++){
			map[j]=sc.next().toCharArray();
		}

		@SuppressWarnings("unchecked")
		LinkedList<P>[] lists=new LinkedList[type];
		for(int i=0; i<type; i++){
			lists[i]=new LinkedList<P>();
		}
		for(int j=0; j<n; j++){
			for(int i=0; i<m; i++){
				if(map[j][i]!='.'){
					lists[map[j][i]-'A'].add(new P(i, j));
				}
			}
		}
		
		pList=new LinkedList<P>();
		for(int i=0; i<type; i++){
			if(lists[i].size()==2){
				P p1=lists[i].get(0);
				P p2=lists[i].get(1);
				if(p1.x==p2.x){
					for(int y=min(p1.y, p2.y)+1; y<max(p1.y, p2.y); y++){
						pList.add(new P(p1.x, y));
					}
				}else if(p1.y==p2.y){
					for(int x=min(p1.x, p2.x)+1; x<max(p1.x, p2.x); x++){
						pList.add(new P(x, p1.y));
					}
				}else{
					pList.add(new P(p1.x, p2.y));
					pList.add(new P(p2.x, p1.y));
				}
			}
		}

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

		ans=0;
		removed=new boolean[n][m];
		for(;;){
			int s=0;
			for(P p : pList){
				if(map[p.y][p.x]=='.'||removed[p.y][p.x]){}else{
					continue;
				}
				P[] ps=new P[4];
				for(int k=0; k<4; k++){
					ps[k]=get(p.x, p.y, dx[k], dy[k]);
				}
				for(int j=0; j<4; j++){
					for(int i=j+1; i<4; i++){
						if(ps[i]!=null&&ps[j]!=null
								&&map[ps[i].y][ps[i].x]==map[ps[j].y][ps[j].x]){
							removed[ps[i].y][ps[i].x]=true;
							removed[ps[j].y][ps[j].x]=true;
							s+=2;
						}
					}
				}
			}
			ans+=s;
			if(s==0){
				break;
			}
		}
		println(ans+"");
	}

	P get(int x, int y, int dx, int dy){
		for(x+=dx, y+=dy; x>=0&&x<m&&y>=0&&y<n; x+=dx, y+=dy){
			if(!removed[y][x]&&map[y][x]!='.'){
				return new P(x, y);
			}
		}
		return null;
	}

	class P{
		int x, y;

		P(int x, int y){
			this.x=x;
			this.y=y;
		}

		@Override
		public String toString(){
			return "("+x+","+y+")";
		}
	}

	void debug(Object... os){
		System.err.println(Arrays.deepToString(os));
	}

	void print(String s){
		System.out.print(s);
	}

	void println(String s){
		System.out.println(s);
	}

	public static void main(String[] args){
		// System.setOut(new PrintStream(new BufferedOutputStream(System.out)));
		new Main().run();
	}
}