Problem p00742 (C++) — buggy submission, verdict: Time Limit Exceeded

#include <algorithm>
#include <iostream>
#include <map>
#include <string>
#include <vector>
using namespace std;

const int MAX_N = 12;
typedef pair<int, char> P;

// 入力
int n;
string s[MAX_N];
// f[c] := 文字 c の係数.
int f[256] = {0};
// m[c] := 文字 c に割り当てた数字 (0-9のどれか, まだ割り当てられていないときは
// -1 )
int m[256] = {0};
// max_digit := 最大の桁数
int max_digit;
// 登場する文字
vector<char> vc;
// is_head[c] := 文字 c が先頭で数字 0 を割り当てることができない.
bool is_head[256];
// used[i] := 数字 i を使ったかどうか.
bool used[10];
// 解
int ans;
// 上限と下限
int low, high;

void debug_1() {
  for (int i = 0; i < n; i++) {
    cout << s[i];
    if (i == n - 1) {
      cout << endl;
    } else if (i == n - 2) {
      cout << " = ";
    } else {
      cout << " + ";
    }
  }
}

void debug_2() {
  for (char c = 'A'; c <= 'Z'; c++) {
    if (f[c] == 0)
      continue;
    cout << f[c] << c << ", ";
  }
  cout << endl;
  for (int i = 0; i < vc.size(); i++) {
    cout << vc[i];
  }
  cout << endl;
  cout << "[" << low << "," << high << "]" << endl;
}

int pow10(int x) {
  int a = 1;
  for (int i = 0; i < x; i++)
    a *= 10;
  return a;
}

// 解が明らかに存在しないかどうかチェック.
bool is_answer_exist() {
  int a = s[0].size();
  for (int i = 1; i < n - 1; i++) {
    a = max(a, (int)s[i].size());
  }
  max_digit = max(a, (int)s[n - 1].size());
  // 右辺の桁数が左辺の最大桁数+2以上 か
  // 右辺の桁数が左辺の桁数より小さいとき解はない.
  if (a + 2 <= s[n - 1].size() || a > s[n - 1].size()) {
    return false;
  }
  return true;
}

void init() {
  // 初期化.
  vc.clear();
  for (int i = 0; i < 10; i++) {
    used[i] = false;
  }
  for (char c = 'A'; c <= 'Z'; c++) {
    f[c] = 0;
    m[c] = -1;
  }
  int a = s[0].size();
  for (int i = 1; i < n - 1; i++) {
    a = max(a, (int)s[i].size());
  }
  max_digit = max(a, (int)s[n - 1].size());
  // 文字列を逆にする.
  for (int i = 0; i < n; i++) {
    reverse(s[i].begin(), s[i].end());
  }

  // 各文字の係数をまとめておく.
  int h[256] = {0};
  for (int i = 0; i < n; i++) {
    for (int j = 0; j < max_digit; j++) {
      if (s[i].size() <= j)
        continue;

      h[s[i][j]] = 1;
      if (i == n - 1) {
        f[s[i][j]] -= pow10(j);
      } else {
        f[s[i][j]] += pow10(j);
      }
    }
  }
  // 登場する文字のチェック.
  for (char c = 'A'; c <= 'Z'; c++) {
    if (h[c])
      vc.push_back(c);
  }
  // 係数の小さい順に文字の並び替え
  vector<P> v;
  for (int i = 0; i < vc.size(); i++) {
    char c = vc[i];
    v.push_back(P(f[c], c));
  }
  sort(v.begin(), v.end());
  // 上限と下限の計算
  int p = 9;
  high = low = 0;
  for (int i = 0; i < v.size() && v[i].first < 0; i++) {
    high -= v[i].first * p--;
  }
  p = 9;
  for (int i = v.size() - 1; i >= 0 && v[i].first >= 0; i--) {
    low -= v[i].first * p--;
  }
  // debug_2();

  // 逆にした文字列を元に戻す.
  for (int i = 0; i < n; i++) {
    reverse(s[i].begin(), s[i].end());
  }
}

int check() {
  int sum = 0;
  for (char c = 'A'; c <= 'Z'; c++) {
    if (m[c] == -1)
      continue;
    sum += m[c] * f[c];
  }
  return sum;
}

void solve(int pos, int k, int sum) {
  if (pos == k) {
    if (sum == 0) {
      ans++;
    }
    return;
  }
  if (sum < low || high < sum)
    return;

  // c := 今割り当てようとしている文字
  char c = vc[pos];
  for (int i = 0; i < 10; i++) {
    if (used[i])
      continue;
    if (i == 0 && is_head[c])
      continue;

    used[i] = true;
    m[c] = i;
    solve(pos + 1, k, sum + m[c] * f[c]);
    used[i] = false;
    m[c] = -1;
  }
}

int main() {
  while (cin >> n, n) {
    // 初期化
    for (char c = 'A'; c <= 'Z'; c++)
      is_head[c] = false;
    // 入力.
    for (int i = 0; i < n; i++) {
      cin >> s[i];
      if (s[i].size() >= 2) {
        is_head[s[i][0]] = true;
      }
    }
    // debug_1();

    // 前処理.
    init();
    // 解く.
    ans = 0;
    solve(0, vc.size(), 0);
    // 解を出力.
    cout << ans << endl;
  }
}

Error: TLE

Fixed submission:

#include <algorithm>
#include <iostream>
#include <map>
#include <string>
#include <vector>
using namespace std;

int ans;

template <class T> void debug(vector<T> v) {
  for (int i = 0; i < v.size(); i++) {
    cout << v[i];
    if (i == v.size() - 2)
      cout << " = ";
    else if (i == v.size() - 1)
      cout << endl;
    else
      cout << " + ";
  }
}

void debug2(map<char, char> h) {
  for (map<char, char>::iterator it = h.begin(); it != h.end(); ++it) {
    cout << "h[" << it->first << "] = " << it->second << endl;
  }
}

int pow10(int x) {
  int a = 1;
  for (int i = 0; i < x; i++)
    a *= 10;
  return a;
}

// string型からint型に変換する
int s_to_i(string s) {
  bool flag = false;
  int n = 0;
  for (unsigned int i = 0; i < s.size(); ++i) {
    if (i == 0 && s[i] == '-') {
      flag = true;
    } else if (s[i] >= '0' && s[i] <= '9') {
      n *= 10;
      n += s[i] - '0';
    } else {
      break;
    }
  }
  return (flag) ? -n : n;
}

// 初めにn個からk個選ぶ組合せを返す(32bitのintを返す)
int first(int n) { return ((1 << n) - 1); }

// 次の組合せを返す (32bitのintを返す)
int nextSet(int x) {
  int small, ripple, newSmall, ones;
  small = x & -x;
  ripple = x + small;
  newSmall = ripple & -ripple;
  ones = ((newSmall / small) >> 1) - 1;
  return (ripple | ones);
}

// bit列に対応する文字の列stringを返す ( i番目のbitが1 =>
// i番目の要素を選んでいる, i番目のbitが0 => i番目の要素を選んでいない)
string setComb(int s, int n, string a) {
  string c;
  for (int i = 0; i < n; i++) {
    if (s & 1)
      c.push_back(a[i]);
    s >>= 1;
  }
  return c;
}

bool equal_(vector<int> p) {
  int sum = 0;
  for (int i = 0; i < p.size() - 1; i++) {
    sum += p[i];
  }
  return (sum == p[p.size() - 1]);
}

bool is_number(string s) {
  if (s == "0")
    return true;
  return (s[0] != '0');
}

// vs[i][j] を 1回の再帰で1つずつ数字に変換する
// h[c] : 文字 c と 数字 '0'-'9' の対応
// memo[c] : 数字 c('0'-'9') を使ったかどうか
// sum := 左辺の総和
// right_sum := 右辺の総和
// max_num : 探索を打ちきる上限
void dfs(const vector<string> &vs, int i, int j, map<char, char> h,
         map<char, bool> memo, int sum, int right_sum) {
  if (i < vs.size() - 1 && sum > right_sum)
    return;

  // n-1 番目の文字列を調べるとき
  if (i == vs.size() - 1) {
    char c = vs[i][j];
    // i 番目の文字列の最後の文字まで調べたら
    if (j == vs[i].size()) {
      dfs(vs, i - 1, 0, h, memo, sum, right_sum);
    } else {
      char c = vs[i][j];
      // すでに文字 c に数字が割り振られているとき
      if (h[c]) {
        // 文字 c を 数字(h[c] - '0' == 0から9 ) に置き換えその分の数字を加算
        int add = pow10(vs[i].size() - j - 1) * (h[c] - '0');
        dfs(vs, i, j + 1, h, memo, sum, right_sum + add);
      } else {
        for (char num = '0'; num <= '9'; num++) {
          if (j == 0 && vs[i].size() > 1 && num == '0')
            continue;

          if (!memo[num]) {
            memo[num] = true;
            h[c] = num;
            // 文字 c を 数字(h[c] - '0' == 0から9 )
            // に置き換えその分の数字を加算
            int add = pow10(vs[i].size() - j - 1) * (h[c] - '0');
            dfs(vs, i, j + 1, h, memo, sum, right_sum + add);

            h[c] = 0;
            memo[num] = false;
          }
        }
      }
    }
  } else {
    // i 番目の文字列の最後の文字まで調べたら
    if (j == vs[i].size()) {
      // 最後まで調べたとき
      if (i == 0) {
        if (sum == right_sum)
          ans++;
      } else {
        dfs(vs, i - 1, 0, h, memo, sum, right_sum);
      }
    } else {
      char c = vs[i][j];
      // すでに文字 c に数字が割り振られているとき
      if (h[c]) {
        // 文字 c を 数字(h[c] - '0' == 0から9 ) に置き換えその分の数字を加算
        int add = pow10(vs[i].size() - j - 1) * (h[c] - '0');
        dfs(vs, i, j + 1, h, memo, sum + add, right_sum);
      } else {
        for (char num = '0'; num <= '9'; num++) {
          if (j == 0 && vs[i].size() > 1 && num == '0')
            continue;

          if (!memo[num]) {
            memo[num] = true;
            h[c] = num;
            // 文字 c を 数字(h[c] - '0' == 0から9 )
            // に置き換えその分の数字を加算
            int add = pow10(vs[i].size() - j - 1) * (h[c] - '0');
            dfs(vs, i, j + 1, h, memo, sum + add, right_sum);

            h[c] = 0;
            memo[num] = false;
          }
        }
      }
    }
  }
}

void solve(const vector<string> &vs) {
  // 文字と数字の対応
  // c := 'A' - 'Z', h[c] := '0' - '9'
  map<char, char> h;
  for (char c = 'A'; c <= 'Z'; c++) {
    h[c] = 0;
  }
  // c := '0' - '9', memo[c] := 数字 c を使ったかどうか
  map<char, bool> memo;
  for (char c = '0'; c <= '9'; c++) {
    memo[c] = false;
  }

  // 各文字に数字を割り当てていく
  dfs(vs, vs.size() - 1, 0, h, memo, 0, 0);
}

// 左辺の最大の桁数 + 2 >= 右辺の桁数
// 左辺の最大の桁数 <= 右辺の桁数
// 上の二つを満たすかどうか
bool check(const vector<string> &vs) {
  int left_digit = vs[0].size();
  int right_digit = vs[vs.size() - 1].size();

  for (int i = 1; i < vs.size() - 1; i++) {
    left_digit = max(left_digit, (int)vs[i].size());
  }
  if (left_digit + 2 >= right_digit && left_digit <= right_digit)
    return true;
  return false;
}

int main() {
  int n;
  while (cin >> n, n) {
    // 入力する n 個の文字列
    vector<string> vs(n);

    // 文字列の入力
    for (int i = 0; i < n; i++) {
      cin >> vs[i];
    }

    ans = 0;
    // 左辺の最大の桁数 + 2 >= 右辺の桁数
    // 左辺の最大の桁数 <= 右辺の桁数
    // 上の二つを満たすかどうか
    if (check(vs)) {
      solve(vs);
    }

    // 解の出力
    cout << ans << endl;
  }
}