#include <bits/stdc++.h>
using namespace std;
using ll = long long;
using ull = unsigned long long;
int n;
string A, B;
vector<ll> pot10;
vector<int> primes;
ll maxValue(const string& s) {
ll x = 0;
for (char c : s)
x = x * 10 + (c == '?' ? 9 : c - '0');
return x;
}
ll parseNumber(const string& s) {
ll x = 0;
for (char c : s)
x = x * 10 + (c - '0');
return x;
}
int countQuestions(const string& s) {
return count(s.begin(), s.end(), '?');
}
bool matches(const string& pattern, ll x) {
if (x <= 0) return false;
string s = to_string(x);
if ((int)s.size() > (int)pattern.size())
return false;
s = string(pattern.size() - s.size(), '0') + s;
for (int i = 0; i < (int)pattern.size(); ++i) {
if (pattern[i] != '?' && pattern[i] != s[i])
return false;
}
return true;
}
// Czy istnieje dodatnie uzupełnienie patternu
// podzielne przez d? DP po resztach modulo d.
bool hasMultipleDP(const string& pattern, int d) {
vector<array<unsigned char, 2>> cur(d), nxt(d);
for (auto& x : cur) x = {0, 0};
cur[0][0] = 1;
for (char c : pattern) {
for (auto& x : nxt) x = {0, 0};
int lo = (c == '?' ? 0 : c - '0');
int hi = (c == '?' ? 9 : c - '0');
for (int r = 0; r < d; ++r) {
for (int nz = 0; nz <= 1; ++nz) {
if (!cur[r][nz]) continue;
for (int digit = lo; digit <= hi; ++digit) {
int nr = (int)(((ll)r * 10 + digit) % d);
int nnz = nz || digit != 0;
nxt[nr][nnz] = 1;
}
}
}
cur.swap(nxt);
}
return cur[0][1];
}
ll extgcd(ll a, ll b, ll& x, ll& y) {
if (b == 0) {
x = 1;
y = 0;
return a;
}
ll x1, y1;
ll g = extgcd(b, a % b, x1, y1);
x = y1;
y = x1 - (a / b) * y1;
return g;
}
ll modInverse(ll a, ll mod) {
ll x, y;
extgcd(a, mod, x, y);
x %= mod;
if (x < 0) x += mod;
return x;
}
// Czy pattern ma dodatnią wielokrotność d?
// Jeżeli końcówka patternu zawiera ustalone cyfry,
// wykorzystujemy kongruencję modulo 10^k.
bool hasMultiple(const string& pattern, ll maxVal, ll d) {
if (d == 1) return maxVal >= 1;
ll cnt = maxVal / d;
int k = 0;
while (k < (int)pattern.size() &&
pattern[(int)pattern.size() - 1 - k] != '?') {
++k;
}
if (k > 0) {
ll mod10 = 1;
ll rem = 0;
for (int i = (int)pattern.size() - k;
i < (int)pattern.size(); ++i) {
rem = rem * 10 + (pattern[i] - '0');
mod10 *= 10;
}
ll g = __gcd(d, mod10);
if (rem % g != 0) return false;
ll step = mod10 / g;
ll first;
if (step == 1) {
first = 1;
} else {
ll dd = (d / g) % step;
ll rr = (rem / g) % step;
first = (rr * modInverse(dd, step)) % step;
if (first == 0) first = step;
}
for (ll t = first; t <= cnt; t += step) {
if (matches(pattern, d * t))
return true;
}
return false;
}
// Bez ustalonej końcówki wybieramy tańszą metodę.
if (d <= 100000 && cnt > 90 * d)
return hasMultipleDP(pattern, (int)d);
for (ll t = 1, x = d; t <= cnt; ++t, x += d) {
if (matches(pattern, x))
return true;
}
return false;
}
// Dzielniki x w kolejności malejącej.
vector<ll> getDivisors(ll x) {
vector<pair<ll, int>> fac;
ll z = x;
for (int p : primes) {
if (1LL * p * p > z) break;
if (z % p == 0) {
int e = 0;
while (z % p == 0) {
z /= p;
++e;
}
fac.push_back({p, e});
}
}
if (z > 1)
fac.push_back({z, 1});
vector<ll> divs{1};
for (auto [p, e] : fac) {
size_t oldSize = divs.size();
ll mul = 1;
for (int j = 1; j <= e; ++j) {
mul *= p;
for (size_t i = 0; i < oldSize; ++i)
divs.push_back(divs[i] * mul);
}
}
sort(divs.rbegin(), divs.rend());
return divs;
}
// Stan DP dla mnożenia p*g i q*g.
// ca, cb: przeniesienia obu mnożeń.
// hist: ostatnie cyfry g potrzebne do kolejnych kolumn.
struct State {
int ca, cb, hist;
bool operator==(const State& o) const {
return ca == o.ca && cb == o.cb && hist == o.hist;
}
};
struct StateHash {
static ull mix(ull x) {
x += 0x9e3779b97f4a7c15ULL;
x = (x ^ (x >> 30)) * 0xbf58476d1ce4e5b9ULL;
x = (x ^ (x >> 27)) * 0x94d049bb133111ebULL;
return x ^ (x >> 31);
}
size_t operator()(const State& s) const {
ull h = mix((unsigned)s.ca);
h ^= mix((unsigned)s.cb + 0x123456789ULL);
h ^= mix((unsigned)s.hist + 0x987654321ULL);
return (size_t)h;
}
};
vector<int> digitsLE(int x) {
vector<int> d;
while (x > 0) {
d.push_back(x % 10);
x /= 10;
}
if (d.empty()) d.push_back(0);
return d;
}
// Największe g takie, że p*g pasuje do A,
// q*g pasuje do B. Wywołujemy dla __gcd(p,q)=1.
ll solveRatio(int p, int q) {
vector<int> pd = digitsLE(p);
vector<int> qd = digitsLE(q);
int t = max(pd.size(), qd.size());
int base = 1;
for (int i = 0; i < t - 1; ++i)
base *= 10;
vector<int> histPow(max(1, t), 1);
for (int i = 1; i < t; ++i)
histPow[i] = histPow[i - 1] * 10;
// Przetwarzamy również pozycje nad cyframi wyniku,
// aby wymusić, że oba iloczyny mieszczą się w n cyfrach.
int L = n + t;
unordered_map<State, ll, StateHash> cur, nxt;
cur.reserve(256);
nxt.reserve(256);
cur[{0, 0, 0}] = 0;
ll answer = 0;
for (int i = 0; i < L; ++i) {
nxt.clear();
if (cur.size() < 2000000)
nxt.reserve(cur.size() * 2 + 16);
int zhi = (i < n ? 9 : 0);
for (const auto& [st, val] : cur) {
for (int z = 0; z <= zhi; ++z) {
ll sa = st.ca;
ll sb = st.cb;
// Cyfra pozycji i w iloczynie p*g.
for (int j = 0; j < (int)pd.size(); ++j) {
int k = i - j;
if (k < 0) break;
int gd;
if (k >= n) gd = 0;
else if (j == 0) gd = z;
else gd = (st.hist / histPow[j - 1]) % 10;
sa += (ll)pd[j] * gd;
}
// Cyfra pozycji i w iloczynie q*g.
for (int j = 0; j < (int)qd.size(); ++j) {
int k = i - j;
if (k < 0) break;
int gd;
if (k >= n) gd = 0;
else if (j == 0) gd = z;
else gd = (st.hist / histPow[j - 1]) % 10;
sb += (ll)qd[j] * gd;
}
int da = sa % 10;
int db = sb % 10;
int ca2 = sa / 10;
int cb2 = sb / 10;
bool okA =
(i >= n ? da == 0 :
(A[n - 1 - i] == '?' ||
A[n - 1 - i] - '0' == da));
bool okB =
(i >= n ? db == 0 :
(B[n - 1 - i] == '?' ||
B[n - 1 - i] - '0' == db));
if (!okA || !okB) continue;
if (i == L - 1 && (ca2 != 0 || cb2 != 0))
continue;
int nh = (base == 1 ? 0 :
(int)(((ll)st.hist * 10 + z) % base));
ll nv = val;
if (i < n)
nv += (ll)z * pot10[i];
if (i == L - 1) {
answer = max(answer, nv);
} else {
State ns{ca2, cb2, nh};
auto it = nxt.find(ns);
if (it == nxt.end())
nxt.emplace(ns, nv);
else
it->second = max(it->second, nv);
}
}
}
cur.swap(nxt);
if (cur.empty() && i < L - 1)
return 0;
}
return answer;
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
cin >> n >> A >> B;
// Sito liczb pierwszych do rozkładu liczb < 10^9.
vector<bool> isPrime(31624, true);
isPrime[0] = isPrime[1] = false;
for (int i = 2; 1LL * i * i < (int)isPrime.size(); ++i) {
if (isPrime[i]) {
for (int j = i * i; j < (int)isPrime.size(); j += i)
isPrime[j] = false;
}
}
for (int i = 2; i < (int)isPrime.size(); ++i) {
if (isPrime[i]) primes.push_back(i);
}
pot10.assign(n, 1);
for (int i = 1; i < n; ++i)
pot10[i] = pot10[i - 1] * 10;
int qa = countQuestions(A);
int qb = countQuestions(B);
ll maxA = maxValue(A);
ll maxB = maxValue(B);
// Para maksymalnych uzupełnień już osiąga górną granicę.
ll initialBest = __gcd(maxA, maxB);
if (initialBest == min(maxA, maxB)) {
cout << initialBest << '\n';
return 0;
}
// Jedna liczba jest całkowicie dowolna.
if (qa == n) {
cout << maxB << '\n';
return 0;
}
if (qb == n) {
cout << maxA << '\n';
return 0;
}
// Jeden napis jest ustaloną liczbą.
if (qa == 0 || qb == 0) {
ll x = (qa == 0 ? parseNumber(A) : parseNumber(B));
const string& pat = (qa == 0 ? B : A);
ll maxPat = (qa == 0 ? maxB : maxA);
ll best = __gcd(x, maxPat);
auto divs = getDivisors(x);
for (ll d : divs) {
if (d <= best) break;
if (hasMultiple(pat, maxPat, d)) {
best = d;
break;
}
}
cout << best << '\n';
return 0;
}
// Oba napisy mają najwyżej 5 znaków zapytania.
// Zbieramy wszystkie dzielniki uzupełnień jednego z nich.
if (max(qa, qb) <= 5) {
const string& pat = (qa >= qb ? A : B);
const string& other = (qa >= qb ? B : A);
vector<int> wildOther;
for (int i = 0; i < n; ++i) {
if (other[i] == '?') wildOther.push_back(i);
}
string so = other;
vector<uint32_t> allDivisors;
function<void(int)> build = [&](int pos) {
if (pos == (int)wildOther.size()) {
ll y = parseNumber(so);
if (y == 0) return;
auto divs = getDivisors(y);
for (ll d : divs)
allDivisors.push_back((uint32_t)d);
return;
}
int idx = wildOther[pos];
for (int digit = 0; digit <= 9; ++digit) {
so[idx] = char('0' + digit);
build(pos + 1);
}
};
build(0);
sort(allDivisors.begin(), allDivisors.end());
allDivisors.erase(
unique(allDivisors.begin(), allDivisors.end()),
allDivisors.end()
);
vector<int> wildPat;
for (int i = 0; i < n; ++i) {
if (pat[i] == '?') wildPat.push_back(i);
}
string sp = pat;
ll best = __gcd(maxA, maxB);
function<void(int)> search = [&](int pos) {
if (maxValue(sp) <= best) return;
if (pos == (int)wildPat.size()) {
ll x = parseNumber(sp);
if (x == 0) return;
auto divs = getDivisors(x);
for (ll d : divs) {
if (d <= best) break;
if (binary_search(
allDivisors.begin(),
allDivisors.end(),
(uint32_t)d)) {
best = d;
break;
}
}
return;
}
int idx = wildPat[pos];
for (int digit = 9; digit >= 0; --digit) {
sp[idx] = char('0' + digit);
search(pos + 1);
}
sp[idx] = '?';
};
search(0);
cout << best << '\n';
return 0;
}
// Jeden napis ma najwyżej 5 znaków zapytania,
// drugi ma ich więcej. Enumerujemy uzupełnienia
// mniej elastycznego napisu.
if (min(qa, qb) <= 5) {
const string& pat = (qa <= qb ? A : B);
const string& other = (qa <= qb ? B : A);
ll maxOther = (qa <= qb ? maxB : maxA);
vector<int> vars;
for (int i = 0; i < n; ++i) {
if (pat[i] == '?') vars.push_back(i);
}
string s = pat;
ll best = __gcd(maxA, maxB);
unordered_map<ll, unsigned char> cache;
cache.reserve(100000);
function<void(int)> dfs = [&](int pos) {
if (maxValue(s) <= best) return;
if (pos == (int)vars.size()) {
ll x = parseNumber(s);
if (x == 0) return;
auto divs = getDivisors(x);
for (ll d : divs) {
if (d <= best) break;
auto it = cache.find(d);
bool ok;
if (it != cache.end()) {
ok = it->second;
} else {
ok = hasMultiple(other, maxOther, d);
cache.emplace(d, (unsigned char)ok);
}
if (ok) {
best = d;
break;
}
}
return;
}
int idx = vars[pos];
for (int digit = 9; digit >= 0; --digit) {
s[idx] = char('0' + digit);
dfs(pos + 1);
}
s[idx] = '?';
};
dfs(0);
cout << best << '\n';
return 0;
}
// Pozostał trudny przypadek: oba napisy mają co najmniej
// 6 znaków zapytania.
ll best = __gcd(maxA, maxB);
// Szybkie, poprawne uzupełnienia dające dolne ograniczenie.
for (int digit = 0; digit <= 9; ++digit) {
string a = A, b = B;
for (char& c : a)
if (c == '?') c = char('0' + digit);
for (char& c : b)
if (c == '?') c = char('0' + digit);
ll x = parseNumber(a);
ll y = parseNumber(b);
if (x && y)
best = max(best, __gcd(x, y));
}
// Jeśli oba napisy dają się uzupełnić do tej samej liczby,
// sprawdzamy również największe wspólne uzupełnienie.
bool compatible = true;
string c = A;
for (int i = 0; i < n; ++i) {
if (A[i] != '?' && B[i] != '?' && A[i] != B[i])
compatible = false;
if (A[i] == '?' && B[i] != '?')
c[i] = B[i];
else if (A[i] != '?')
c[i] = A[i];
else
c[i] = '9';
}
if (compatible) {
ll x = parseNumber(c);
best = max(best, x);
}
if (best >= min(maxA, maxB)) {
cout << best << '\n';
return 0;
}
// Każde rozwiązanie o NWD g > best ma postać:
// x = p*g, y = q*g, gdzie __gcd(p,q)=1.
// Wtedy p <= maxA/(best+1), q <= maxB/(best+1).
for (ll m = 1;; ++m) {
ll limP = maxA / (best + 1);
ll limQ = maxB / (best + 1);
if (m > max(limP, limQ)) break;
int upP = (int)min(m, limP);
int upQ = (int)min(m, limQ);
for (int p = 1; p <= upP; ++p) {
for (int q = 1; q <= upQ; ++q) {
if (max(p, q) != m) continue;
if (__gcd(p, q) != 1) continue;
ll g = solveRatio(p, q);
best = max(best, g);
}
}
}
cout << best << '\n';
return 0;
}