#include<iostream>
#include<string>
#include<set>
#include<vector>
#include<algorithm>
#include<stack>
#include<cmath>
#include<map>
#include<bitset>

using namespace std;

typedef long long ll;
typedef pair<vector<int>, int> Pvi;
typedef pair<int, int> Pli;

int N;
string implicant[100];
int variation = 0;
bool f;

bool ban0[10];
vector<int> keisu;

map<Pli, int> zenhan, kohan;
//zenhan kohanは{合計値, 使用状況, 存在してるのであれば1　そうでなければ++されない}

int mid;//二つに分ける半分全列挙の境目の値　[0, mid), [mid, variation)の二つに分けて列挙する

int fact(int a) {
	int ret = 1;
	for (int i = 1; i <= a; i++)ret *= i;
	return ret;
}

void convert() {
	//implicantの文字をすべて圧縮して、ABCDEFGHIJの最大10文字にする
	char con[200] = {};
	bool letter[26] = {};
	for (int i = 0; i < N; i++) {
		for (int j = 0; j < implicant[i].size(); j++) {
			letter[implicant[i][j] - 'A'] = true;;
		}
	}

	int CNT = 0;
	while (CNT < 26) {
		if (!letter[CNT]);
		else {
			char now = 'A' + CNT;
			con[now] = 'A' + variation;
			variation++;
		}
		CNT++;
	}

	for (int i = 0; i < N; i++) {
		for (int j = 0; j < implicant[i].size(); j++) {
			implicant[i][j] = con[implicant[i][j]];
		}
	}
}

void dfs(short int cnt, int sum, int bit) {
	if (!f && cnt == mid) {
		zenhan[Pli(sum, bit)]++;
		return;
	}
	else if (f && cnt == variation) {
		kohan[Pli(sum, bit)]++;
		return;
	}

	for (int i = 0; i <= 9; i++) {
		if (i == 0 && ban0[cnt])continue;
		if (bit & (1 << i))continue;
		dfs(cnt + 1, sum + keisu[cnt] * i, bit | (1 << i));
	}

}

int main() {
	while (1) {
		cin >> N;
		if (N == 0)break;
		for (int i = 0; i < N; i++) {
			cin >> implicant[i];
		}

		//すべての出現する文字をABCDEFGHIJの十文字にそろえてる
		convert();

		//0になってはいけないアルファベットをピックアップ
		for (int i = 0; i < N; i++) {
			if (implicant[i].size() != 1)ban0[implicant[i][0] - 'A'] = true;;
		}

		//各項の係数をまとめる
		keisu.resize(variation);
		for (int i = 0; i < N; i++) {
			for (int j = 0; j < implicant[i].size(); j++) {
				if (i != N - 1) {
					keisu[implicant[i][j] - 'A'] += pow(10, implicant[i].size() - 1 - j);
				}
				else {
					keisu[implicant[i][j] - 'A'] -= pow(10, implicant[i].size() - 1 - j);
				}
			}
		}

		//半分全列挙のために、文字を前半後半にわける
		mid = N / 2;
		f = false;
		dfs(0, 0, 0);
		f = true;
		dfs(mid, 0, 0);
		
		//stackからベクターに流し込む　取扱しやすいように setのイテレータ順次アクセスは効率悪いけどここは割り切る
		vector<pair<Pli, int>> zenhanv(zenhan.begin(), zenhan.end()), kohanv(kohan.begin(), kohan.end());
		
		for (int i = 0; i < kohanv.size(); i++)kohanv[i].first.first *= -1;

		//尺取り法を用いた数え上げ　どちらもSumは昇順なので、前半のi番目と釣りあう可能性のあるものは、後半のj番以降だと、j-1番はありえない
		//単調性があるのでしゃくとりの手法が使える。もちろん、前半の数ごとに二分探索してもよい

		int ans = 0;
		int l = 0, r = kohanv.size() - 1;
		for (; l < zenhanv.size();) {
			while (r > 0 && zenhanv[l].first.first > kohanv[r].first.first)r--;
			int s1 = zenhanv[l].first.first, s2 = kohanv[r].first.first;

			if (zenhanv[l].first.first != kohanv[r].first.first) {
				while (l < zenhanv.size() && zenhanv[l].first.first == s1)l++;
				continue;
			}
			for (int i = 0; l + i < zenhanv.size() && s1 == zenhanv[l + i].first.first; i++) {
				for (int j = 0; r - j >= 0 && s2 == kohanv[r - j].first.first; j++) {
					if ((zenhanv[l + i].first.second & kohanv[r - j].first.second) == 0) {
						ans += zenhanv[l + i].second * kohanv[r - j].second;
					}
				}
			}
			while (l < zenhanv.size() && zenhanv[l].first.first == s1)l++;
		}

		cout << ans << endl;

		for (int i = 0; i < N; i++)implicant[i] = "";
		variation = 0;
		keisu.clear();
		zenhan.clear(), kohan.clear();
		for (int i = 0; i <= 9; i++)ban0[i] = false;
	}
	return 0;
}


Measured memory_bytes: 110760