#include <vector>
#include <map>
#include <set>
#include <stack>
#include <queue>
#include <algorithm>
#include <utility>
#include <functional>
#include <sstream>
#include <iostream>
#include <cstdio>
#include <cmath>
#include <cstdlib>
#include <cctype>
#include <string>
#include <cstring>
#include <ctime>
#include <climits>
#include <fstream>
using namespace std;
inline int toInt(string s) { int v; istringstream sin(s); sin >> v; return v;}
template<class T> inline string toStr(T x) { ostringstream sout; sout << x; return sout.str();}
typedef vector<int> vi;
typedef vector<vi>  vvi;
typedef vector<string> vs;
typedef pair<int, int> pii;
typedef long long ll;
#define ALL(a) (a).begin(),(a).end()
#define RALL(a) (a).rbegin(),(a).rend()
#define FOR(i,a,b) for(int i=(a);i<=(b);++i)
#define REP(i,n) FOR(i,0,(n)-1)
const double EPS = 1e-12;
const double PI = acos(-1.0);
const int INF = INT_MAX/10;

const int NONE = 0;
const int VERTICAL = 1;
const int HORIZONTAL = 2;
const int CORNER = 3;

int main() {
	int M, N;
	cin >> M >> N;

	vs field(M);
	vvi pos(26, vi(4, -1));
	REP(i, M) {
		cin >> field[i];
		REP(j, N) {
			if(field[i][j] != '.') {
				int k = field[i][j]-'A';
				if(pos[k][0] == -1) {
					pos[k][0] = i;
					pos[k][1] = j;
				} else {
					pos[k][2] = i;
					pos[k][3] = j;
				}
			}
		}
	}

	vi type(26, CORNER);
	REP(i, 26) {
		if(pos[i][2] == -1) {
			type[i] = NONE;
			continue;
		}
		if(abs(pos[i][0]-pos[i][2])+abs(pos[i][1]-pos[i][3]) == 1) {
			type[i] = NONE;
			continue;
		}
		if(pos[i][0] == pos[i][2]) {
			type[i] = VERTICAL;
			continue;
		}
		if(pos[i][1] == pos[i][3]) {
			type[i] = HORIZONTAL;
			continue;
		}
	}

	int ans = 0;
	bool updated = true;
	while(updated) {
		updated = false;
		REP(i, 26) {
			if(type[i] == NONE) {
			
			} else if(type[i] == VERTICAL) {
				int y = pos[i][0];
				FOR(x, min(pos[i][1], pos[i][3])+1, max(pos[i][1], pos[i][3])-1) {
					if(field[y][x] != '.') {
						goto next;
					}
				}
				ans += 2;
				type[i] = NONE;
				field[pos[i][0]][pos[i][1]] = field[pos[i][2]][pos[i][3]] = '.';
				updated = true;
				break;
			} else if(type[i] ==  HORIZONTAL) {
				int x = pos[i][1];
				FOR(y, min(pos[i][0], pos[i][2])+1, max(pos[i][0], pos[i][2])-1) {
					if(field[y][x] != '.') {
						goto next;
					}
				}
				ans += 2;
				type[i] = NONE;
				field[pos[i][0]][pos[i][1]] = field[pos[i][2]][pos[i][3]] = '.';
				updated = true;
				break;
			} else if(type[i] == CORNER) {
				int miny = min(pos[i][0], pos[i][2]);
				int maxy = max(pos[i][0], pos[i][2]);
				int minx = min(pos[i][1], pos[i][3]);
				int maxx = max(pos[i][1], pos[i][3]);
				bool li1 = false, li2 = false, li3 = false, li4 = false;

				l1:
				FOR(y, miny, maxy) {
					if(field[y][minx] != '.' && field[y][minx] != 'A'+i) {
						goto l2;
					}
				}
				li1 = true;

				l2:
				FOR(x, minx, maxx) {
					if(field[maxy][x] != '.' && field[maxy][x] != 'A'+i) {
						goto l3;
					}
				}
				li2 = true;

				l3:
				FOR(y, miny, maxy) {
					if(field[y][maxx] != '.' && field[y][maxx] != 'A'+i) {
						goto li4;
					}
				}
				li3 = true;

				li4:
				FOR(x, minx, maxx) {
					if(field[miny][x] != '.' && field[miny][x] != 'A'+i) {
						goto check;
					}
				}
				li4 = true;

				check:
				if(pos[i][0] == miny && pos[i][1] == minx) {
					if((li1&li2) || (li3&li4)) {
						goto ok;
					}
				} else if(pos[i][2] == miny && pos[i][3] == minx) {
					if((li1&li2) || (li3&li4)) {
						goto ok;
					}
				} else {
					if((li1&li4) || (li2&li3)) {
						goto ok;
					}
				}
				goto next;

				ok:;
					ans += 2;
					type[i] = NONE;
					field[pos[i][0]][pos[i][1]] = field[pos[i][2]][pos[i][3]] = '.';
					updated = true;
					break;
			}
			next:;
		}
		/*
		cout << "----------" << endl;
		REP(i, M) {
			cout << field[i] << endl;
		}
		*/
	}

	cout << ans << endl;
	return 0;
}

Measured memory_bytes: 1364