use std::hash::Hash;
use std::collections::hash_set::Iter;
use std::rc::Rc;
use std::ops::DivAssign;
use std::ops::MulAssign;
use std::ops::SubAssign;
use std::collections::BinaryHeap;
use std::str::FromStr;
use std::collections::HashSet;
use std::collections::BTreeMap;
use std::fmt::Display;
use std::ops::Neg;
use std::ops::Div;
use std::ops::Mul;
use std::ops::Add;
use std::ops::{AddAssign, Sub};
use std::cmp::max;
use std::collections::VecDeque;
use std::cmp::min;
use std::collections::{HashMap, BTreeSet};
use std::cmp::Ordering;
use std::fmt::Debug;

fn read_line() -> String {
    let mut buffer = String::new();
    std::io::stdin().read_line(&mut buffer).expect("No Line");
    buffer.trim().to_owned()
}
fn read_lines<T: std::str::FromStr>(count: usize) -> Vec<T> {
    let mut buffer = String::new();
    let mut vec = Vec::with_capacity(count);
    for _ in 0 .. count {
        std::io::stdin().read_line(&mut buffer).expect("No Line");
        vec.push(buffer.trim().parse().ok().expect("Can't Parse"));
        buffer.clear();
    }
    vec
}
fn read_tabulate<R, T: Fn(&str)->R> (count: usize, transformer: T) -> Vec<R> {
    let mut buffer = String::new();
    let mut vec = Vec::with_capacity(count);
    for _ in 0 .. count {
        std::io::stdin().read_line(&mut buffer).expect("No Line");
        vec.push(transformer(buffer.trim()));
        buffer.clear();
    }
    vec
}
fn read_value<T: std::str::FromStr>() -> T {
    read_line().trim().parse().ok().unwrap()
}
fn read_values<T: std::str::FromStr>() -> Vec<T> {
    read_line().trim().split_whitespace().map(|x| x.parse().ok().expect("Can't Parse")).collect::<Vec<T>>()
}
macro_rules! freeze {
    ($($id:ident), *) => {
        $(let $id = $id;)*
    };
}
macro_rules! read_map {
    ($ident: ident: [$block: block; $size: expr]) => {
        let $ident = (0 .. $size).into_iter().map(|_| $block).collect::<Vec<_>>();
    };
    (mut $ident: ident: [$block: block; $size: expr]) => {
        let mut $ident = (0 .. $size).into_iter().map(|_| $block).collect::<Vec<_>>();
    };
}
macro_rules! read {
    (mut $ident: ident: String) => {
        let mut $ident = read_value::<String>();
    };
    ($ident: ident: String) => {
        let $ident = read_value::<String>();
    };
    (mut $ident: ident: [$ty:ty]) => {
        let mut $ident = read_values::<$ty>();
    };
    ($ident: ident: [$ty:ty]) => {
        let $ident = read_values::<$ty>();
    };
    (mut $ident: ident: [[$ty:ty]; $size: expr]) => {
        let mut $ident = (0 .. $size).into_iter().map(|_| read_values::<$ty>()).collect::<Vec<_>>();
    };
    ($ident: ident: [[$ty:ty]; $size: expr]) => {
        let $ident = (0 .. $size).into_iter().map(|_| read_values::<$ty>()).collect::<Vec<_>>();
    };
    (mut $ident: ident: [$ty:ty; $size:expr]) => {
        let mut $ident = read_lines::<$ty>($size);
    };
    ($ident: ident: [$ty:ty; $size:expr]) => {
        let $ident = read_lines::<$ty>($size);
    };
    ($ident: ident: [$block: block; $size: expr]) => {
        let $ident = (0 .. $size).into_iter().map(|_| $block).collect::<Vec<_>>();
    };
    (mut $ident: ident: [$block: block; $size: expr]) => {
        let mut $ident = (0 .. $size).into_iter().map(|_| $block).collect::<Vec<_>>();
    };
    ($($token: tt)*) => {
        let mut iter = read_values::<String>().into_iter();
        read_from_iter!(iter; $($token)*);
    };
}
macro_rules! read_from_iter {
    ($iter:expr; mut $ident:ident:$ty:ty, $($rest:tt)*) => {
        let mut $ident = $iter.next().unwrap().parse::<$ty>().expect("Can't Parse");
        read_from_iter!($iter; $($rest)*);
    };
    ($iter:expr; $ident:ident:$ty:ty, $($rest:tt)*) => {
        let $ident = $iter.next().unwrap().parse::<$ty>().expect("Can't Parse");
        read_from_iter!($iter; $($rest)*);
    };
    ($iter:expr; mut $ident:ident:$ty:ty) => {
        let mut $ident = $iter.next().unwrap().parse::<$ty>().expect("Can't Parse");
    };
    ($iter:expr; $ident:ident:$ty:ty) => {
        let $ident = $iter.next().unwrap().parse::<$ty>().expect("Can't Parse");
    };
    ($iter: expr; ) => {};
}
struct KeyValue<K, V> {
    key: K,
    value: V
}
impl <K: PartialOrd, V> PartialEq for KeyValue<K, V> {
    fn eq(&self, other: &Self) -> bool {
        self.key.eq(&other.key)
    }
}
impl <K: PartialOrd, V> Eq for KeyValue<K, V> {}
impl <K: PartialOrd, V> PartialOrd for KeyValue<K, V> {
    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
        self.key.partial_cmp(&other.key)
    }
}
impl <K: PartialOrd, V> Ord for KeyValue<K, V> {
    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
        self.key.partial_cmp(&other.key).ok_or("Can't Compare").unwrap()
    }
}
#[derive(Eq, PartialEq)]
struct Rev<T: Ord>(T);
impl <T: std::cmp::Ord> PartialOrd for Rev<T> {
    fn partial_cmp(&self, rhs: &Rev<T>) -> std::option::Option<std::cmp::Ordering> { 
        rhs.0.partial_cmp(&self.0)
    }
}
impl <T: Ord> Ord for Rev<T> {
    fn cmp(&self, rhs: &Self) -> std::cmp::Ordering { 
        rhs.0.cmp(&self.0)
    }
}

fn shortcut(expression: &Vec<Vec<usize>>, map: &Vec<usize>, digit: usize) -> bool {
    for e in expression {
        if let Some(last) = (1 .. e.len()).rev().find(|&i| e[i] != 10) {
            if map[e[last]] == 0 {return false;}
        }
    }
    let mut sum = 0;
    for i in 0 .. digit {
        for j in 0 .. expression.len() - 1 {
            sum += map[expression[j][i]];
        }
        if sum % 10 != map[expression[expression.len() - 1][i]] {
            return false;
        }
        sum /= 10;
    }
    true
}
fn is_valid(expression: &Vec<Vec<usize>>, map: &Vec<usize>) -> bool {
    for e in expression {
        if let Some(last) = (1 .. e.len()).rev().find(|&i| e[i] != 10) {
            if map[e[last]] == 0 {return false;}
        }
    }
    let mut sum = 0;
    for i in 0 .. expression[0].len() {
        for j in 0 .. expression.len() - 1 {
            sum += map[expression[j][i]];
        }
        if sum % 10 != map[expression[expression.len() - 1][i]] {
            return false;
        }
        sum /= 10;
    }
    sum == 0
}
fn count_up(expression: &Vec<Vec<usize>>, digit: usize, line: usize, used: &mut Vec<bool>, map: &mut Vec<usize>) -> usize {
    if line == expression.len() {return count_up(expression, digit + 1, 0, used, map);};
    if digit == expression[0].len() && line == 0 { return if is_valid(expression, map) {1}else{0}};
    if line == 0 && !shortcut(expression, map, digit) {return 0};
    if map[expression[line][digit]] < 10 {return count_up(expression, digit, line + 1, used, map);};
    let mut count = 0;
    for i in 0 .. 10 {
        if used[i]{continue;}
        used[i] = true;
        map[expression[line][digit]] = i;
        count += count_up(expression, digit, line + 1, used, map);
        map[expression[line][digit]] = 10;
        used[i] = false;
    }
    count
}
fn fill_digits(expression: &Vec<Vec<usize>>) -> Vec<Vec<usize>> {
    let max_digit = expression.iter().map(|e| e.len()).max().unwrap();
    let mut result = Vec::with_capacity(expression.len());
    for e in expression {
        let mut cloned = e.iter().cloned().rev().collect::<Vec<_>>();
        while cloned.len() < max_digit {
            cloned.push(10usize);
        }
        result.push(cloned);
    }
    result
}
fn normalize(expression: &Vec<String>) -> Vec<Vec<usize>> {
    let mut map = HashMap::<char, usize>::new();
    let mut result = Vec::with_capacity(expression.len());
    for e in expression {
        let mut r = Vec::with_capacity(e.len());
        for c in e.chars() {
            if let Some(&i) = map.get(&c) {
                r.push(i);
            }else {
                let i = map.len();
                map.insert(c, i);
                r.push(i);
            }
        }
        result.push(r);
    }
    result
}
fn count(expression: &Vec<String>) -> usize {
    let mut used = vec![false; 10];
    let mut map = vec![10; 11];
    map[10] = 0;
    count_up(&fill_digits(&normalize(expression)), 0, 0, &mut used, &mut map)
}
fn main() {
    loop {
        read!(n: usize);
        if n == 0 {break;}
        read_map!(expression: [{
            read!(line: String);
            line
        }; n]);
        println!("{}", count(&expression));
    }
}


Measured memory_bytes: 5040