/*
author: Maksim1744
created: 09.03.2024 12:03:07
*/
#include "bits/stdc++.h"
#include <ext/pb_ds/assoc_container.hpp>
#include <ext/pb_ds/tree_policy.hpp>
using namespace std;
using ll = long long;
using ld = long double;
#define mp make_pair
#define pb push_back
#define eb emplace_back
#define sum(a) ( accumulate ((a).begin(), (a).end(), 0ll))
#define mine(a) (*min_element((a).begin(), (a).end()))
#define maxe(a) (*max_element((a).begin(), (a).end()))
#define mini(a) ( min_element((a).begin(), (a).end()) - (a).begin())
#define maxi(a) ( max_element((a).begin(), (a).end()) - (a).begin())
#define lowb(a, x) ( lower_bound((a).begin(), (a).end(), (x)) - (a).begin())
#define uppb(a, x) ( upper_bound((a).begin(), (a).end(), (x)) - (a).begin())
template<typename T> vector<T>& operator-- (vector<T> &v){for (auto& i : v) --i; return v;}
template<typename T> vector<T>& operator++ (vector<T> &v){for (auto& i : v) ++i; return v;}
template<typename T> istream& operator>>(istream& is, vector<T> &v){for (auto& i : v) is >> i; return is;}
template<typename T> ostream& operator<<(ostream& os, vector<T> v){for (auto& i : v) os << i << ' '; return os;}
template<typename T, typename U> pair<T,U>& operator-- (pair<T, U> &p){--p.first; --p.second; return p;}
template<typename T, typename U> pair<T,U>& operator++ (pair<T, U> &p){++p.first; ++p.second; return p;}
template<typename T, typename U> istream& operator>>(istream& is, pair<T, U> &p){is >> p.first >> p.second; return is;}
template<typename T, typename U> ostream& operator<<(ostream& os, pair<T, U> p){os << p.first << ' ' << p.second; return os;}
template<typename T, typename U> pair<T,U> operator-(pair<T,U> a, pair<T,U> b){return mp(a.first-b.first, a.second-b.second);}
template<typename T, typename U> pair<T,U> operator+(pair<T,U> a, pair<T,U> b){return mp(a.first+b.first, a.second+b.second);}
template<typename T, typename U> void umin(T& a, U b){if (a > b) a = b;}
template<typename T, typename U> void umax(T& a, U b){if (a < b) a = b;}
#ifdef HOME
#define SHOW_COLORS
#include "/mnt/c/Libs/tools/print.cpp"
#else
#define show(...) void(0)
#define debugf(fun) fun
#define debugv(var) var
#define mclock void(0)
#define shows void(0)
#define debug if (false)
#define OSTREAM(...) ;
#define OSTREAM0(...) ;
#endif
const int basen = 18;
const ll base = 1e18;
// uncomment to multiply large numbers with fft
// basen should be even, at most 8
// with doubles in fft and basen = 8 works up to Big.v.size() = 1e6 (but fails for 1e6 + 5e4)
// #define BIGINT_USE_FFT
// division works in n^2 * log(base), where n = Big.v.size()
using T = ll;
array<ll, 19> pow10;
struct Big {
vector<ll> v;
bool minus = false;
Big() {}
Big(long long k) {
if (k < 0) {
minus = true;
k = -k;
}
while (k) {
v.push_back(k % base);
k /= base;
}
}
Big(string s) {
if (s[0] == '-') {
s.erase(s.begin());
minus = true;
}
reverse(s.begin(), s.end());
while (s.size() % basen != 0)
s.push_back('0');
reverse(s.begin(), s.end());
for (int i = 0; i < s.size(); i += basen)
v.push_back(stoll(s.substr(i, basen)));
reverse(v.begin(), v.end());
norm();
}
void modk(int k) {
auto push_at = [&](int i) {
while (i + 1 < v.size() && v[i] >= base) {
while (v[i] >= base) {
v[i] -= base;
v[i + 1]++;
}
++i;
}
};
if (k <= basen) {
while (v.size() > 1) {
v.back() %= pow10[k] - 1;
v[v.size() - 2] += v.back() * pow10[basen - k];
v.back() = 0;
push_at(v.size() - 2);
if (v.back() == 0) v.pop_back();
}
norm();
v[0] %= pow10[k] - 1;
return;
}
while (v.size() > k / basen + 1) {
ll x = v.back();
v.back() -= x;
int add_at = ((int)v.size() - 1) * basen % k;
if (add_at % basen == 0) {
v[add_at / basen] += x;
push_at(add_at / basen);
} else {
v[add_at / basen] += pow10[add_at % basen] * (x % pow10[basen - add_at % basen]);
v[add_at / basen + 1] += x / pow10[basen - add_at % basen];
push_at(add_at / basen);
push_at(add_at / basen + 1);
}
if (v.back() == 0) {
v.pop_back();
}
}
while (v.size() == k / basen + 1 && v.back() >= pow10[k - (v.size() - 1) * basen]) {
ll x = v.back() / pow10[k - (v.size() - 1) * basen];
v.back() %= pow10[k - (v.size() - 1) * basen];
v[0] += x;
push_at(0);
}
bool is0 = true;
for (int i = 0; i + 1 < v.size(); ++i) {
if (v[i] != pow10[basen] - 1) {
is0 = false;
break;
}
}
if (v.back() != pow10[k - basen * (v.size() - 1)] - 1) {
is0 = false;
}
if (is0) {
v = {0};
}
norm();
}
void mulint(int x) {
for (ll& u : v)
u *= x;
v.pb(0);
for (int i = 0; i + 1 < v.size(); ++i) {
v[i + 1] += v[i] / base;
v[i] %= base;
}
norm();
}
int modint(int x) {
ll basemod = base % x;
ll ans = 0;
ll pw = 1;
for (ll u : v) {
ans = (ans + u % x * pw) % x;
pw = (pw * basemod) % x;
}
return ans;
}
Big &operator += (const Big &other) {
if (minus == other.minus) {
_add_(v, other.v);
} else {
if (_comp_(other.v, v)) {
_sub_(v, other.v);
} else {
_sub2_(v, other.v);
minus ^= 1;
}
}
norm();
return *this;
}
Big operator + (const Big &other) const {
auto res = *this;
return res += other;
}
Big operator - () const {
Big res = *this;
if (!v.empty()) res.minus ^= 1;
return res;
}
Big &operator -= (const Big &other) {
return *this += -other;
}
Big operator - (const Big &other) const {
auto res = *this;
return res -= other;
}
Big operator * (const Big &other) const {
if (v.empty() || other.v.empty()) return 0;
Big res;
res.v = _mult_(v, other.v);
res.minus = minus ^ other.minus;
return res;
}
Big &operator *= (const Big &other) {
return *this = *this * other;
}
Big operator / (const Big &other) const {
Big res;
res.v = _div_(v, other.v).first;
res.minus = minus ^ other.minus;
res.norm();
return res;
}
Big &operator /= (const Big &other) {
return *this = *this / other;
}
Big operator % (const Big &other) const {
Big res;
res.v = _div_(v, other.v).second;
res.minus = minus ^ other.minus;
res.norm();
return res;
}
Big &operator %= (const Big &other) {
return *this = *this % other;
}
int operator % (int m) const {
long long p = 1;
long long res = 0;
for (int k : v) {
res += k * p % m;
p = p * base % m;
}
return res % m;
}
void norm() {
while (!v.empty() && v.back() == 0)
v.pop_back();
if (v.empty()) {
minus = false;
v.pb(0);
}
}
bool operator < (const Big &other) const {
if (minus != other.minus) return minus;
if (minus) return _comp_(other.v, v);
else return _comp_(v, other.v);
}
bool operator > (const Big &other) const {
return other < *this;
}
bool operator <= (const Big &other) const {
return !(other < *this);
}
bool operator >= (const Big &other) const {
return !(*this < other);
}
bool operator == (const Big &other) const {
return minus == other.minus && v == other.v;
}
bool operator != (const Big &other) const {
return !(*this == other);
}
private:
static void _sub_(vector<T> &a, const vector<T> &b) {
a.resize(max(a.size(), b.size()) + 1, 0);
for (int i = 0; i < b.size(); ++i)
a[i] -= b[i];
for (int i = 0; i + 1 < b.size() || a[i] < 0; ++i) {
if (a[i] < 0) {
a[i] += base;
--a[i + 1];
}
}
assert(a.back() >= 0);
while (!a.empty() && a.back() == 0)
a.pop_back();
}
static void _sub2_(vector<T> &a, const vector<T> &b) {
a.resize(max(a.size(), b.size()) + 1, 0);
for (int i = 0; i < a.size(); ++i)
a[i] = (i < b.size() ? b[i] : 0) - a[i];
for (int i = 0; i + 1 < a.size(); ++i) {
if (a[i] < 0) {
a[i] += base;
--a[i + 1];
}
}
assert(a.back() >= 0);
while (!a.empty() && a.back() == 0)
a.pop_back();
}
static void _add_(vector<T> &a, const vector<T> &b) {
a.resize(max(a.size(), b.size()) + 1, 0);
for (int i = 0; i < b.size(); ++i)
a[i] += b[i];
for (int i = 0; i + 1 < b.size() || a[i] >= base; ++i) {
if (a[i] >= base) {
a[i] -= base;
++a[i + 1];
}
}
while (!a.empty() && a.back() == 0)
a.pop_back();
}
static bool _comp_(const vector<T> &a, const vector<T> &b) {
if (a.size() != b.size())
return a.size() < b.size();
for (int i = (int)a.size() - 1; i >= 0; --i)
if (a[i] != b[i])
return a[i] < b[i];
return false;
}
static vector<T> _mult_(const vector<T> &a, const vector<T> &b) {
#ifdef BIGINT_USE_FFT
// tested on a.v.size() = 1e6, b.v.size() = C, fft is better on C > ~500 : https://ideone.com/kSYLd8
// if a.v.size() = b.v.size() = C, it's 380 : https://ideone.com/MJTo1Y
if (min(a.size(), b.size()) > 380) {
return _fft_mult_(a, b);
}
#endif
return _slow_mult_(a, b);
}
static vector<T> _slow_mult_(const vector<T> &a, const vector<T> &b) {
vector<long long> tmp(a.size() + b.size() + 1, 0);
for (int i = 0; i < a.size(); ++i) {
for (int j = 0; j < b.size(); ++j) {
long long prod = 1ll * a[i] * b[j];
long long div = prod / base;
tmp[i + j] += prod - base * div;
tmp[i + j + 1] += div;
}
}
for (int i = 0; i + 1 < tmp.size(); ++i) {
long long div = tmp[i] / base;
tmp[i + 1] += div;
tmp[i] -= div * base;
}
while (!tmp.empty() && tmp.back() == 0)
tmp.pop_back();
return vector<T>(tmp.begin(), tmp.end());
}
#ifdef BIGINT_USE_FFT
static vector<int> _fft_mult_(const vector<int> &a, const vector<int> &b) {
vector<int> ta(a.size() * 2), tb(b.size() * 2);
static_assert(basen % 2 == 0, "basen has to be even");
const static int M = pow(10, basen / 2);
for (int i = 0; i < a.size(); ++i) {
ta[i * 2] = a[i] % M;
ta[i * 2 + 1] = a[i] / M;
}
for (int i = 0; i < b.size(); ++i) {
tb[i * 2] = b[i] % M;
tb[i * 2 + 1] = b[i] / M;
}
auto tc = fft::multiply(ta, tb);
tc.resize(tc.size() / 2 * 2 + 10, 0);
for (int i = 0; i + 1 < tc.size(); ++i) {
tc[i + 1] += tc[i] / M;
tc[i] %= M;
}
vector<int> res(tc.size() / 2);
for (int i = 0; i < res.size(); ++i)
res[i] = tc[i * 2] + tc[i * 2 + 1] * M;
while (!res.empty() && res.back() == 0)
res.pop_back();
return res;
}
#endif
static pair<vector<T>, vector<T>> _div_(vector<T> a, vector<T> b) {
if (a.size() < b.size()) {
return {{}, a};
}
vector<T> res;
vector<T> c, c2;
for (int i = (int)a.size() - b.size(); i >= 0; --i) {
c.resize(b.size() + i);
for (int j = 0; j < b.size(); ++j) {
c[i + j] = b[j];
}
T L = 0, R = base;
while (R - L > 1) {
T C = (L + R) / 2;
c2 = _mult_(c, {C});
if (_comp_(a, c2)) {
R = C;
} else {
L = C;
}
}
c = _mult_(c, {L});
_sub_(a, c);
res.push_back(L);
}
reverse(res.begin(), res.end());
return {res, a};
}
};
string to_string(const Big &b) {
if (b.v.empty()) return "0";
string res;
for (int i = (int)b.v.size() - 1; i >= 0; --i) {
string t = to_string(b.v[i]);
if (!res.empty())
t = string(basen - t.size(), '0') + t;
res += t;
}
if (b.minus)
res.insert(res.begin(), '-');
return res;
}
ostream &operator << (ostream &o, const Big &b) {
return o << to_string(b);
};
istream &operator >> (istream &i, Big &b) {
string s;
i >> s;
b = Big(s);
return i;
}
Big gcd(Big a, Big b) {
while (b != 0) {
a %= b;
swap(a, b);
}
return a;
}
const int P = 10;
const array<uint32_t, P> primes = {1038315533, 1070784149, 1152186131, 1226633593, 1383110039, 1811889307, 1863415583, 1871039267, 1893815057, 1925743987};
struct hash_my {
size_t operator()(int x) const {
x = ((x >> 16) ^ x) * 0x119de1f3;
x = ((x >> 16) ^ x) * 0x119de1f3;
x = (x >> 16) ^ x;
return x;
}
size_t operator()(long long x) const {
x = (x ^ (x >> 30)) * (0xbf58476d1ce4e5b9ll);
x = (x ^ (x >> 27)) * (0x94d049bb133111ebll);
x = x ^ (x >> 31);
return x;
}
template<typename T, typename U>
size_t operator()(const pair<T, U> &p) const {
long long h1 = (*this)(p.first);
long long h2 = (*this)(p.second);
return (*this)(h1 ^ (h2 << 32) ^ (h2 >> 32));
}
};
auto start_time = clock();
int main() {
ios_base::sync_with_stdio(false); cin.tie(NULL);
pow10[0] = 1;
for (int i = 1; i < pow10.size(); ++i)
pow10[i] = pow10[i - 1] * 10;
// int t;
// cin >> t;
// while (t--) {
// Big a;
// int k;
// cin >> a >> k;
// Big b;
// cin >> b;
// a.modk(k);
// if (a != b) {
// cout << a << endl;
// cout << b << endl;
// show(a.v);
// show(b.v);
// assert(false);
// }
// cerr << "ok" << endl;
// }
map<array<uint32_t, P>, int> cnts;
int n, k;
cin >> n >> k;
vector<Big> v;
for (int i = 0; i < n; ++i) {
string s;
cin >> s;
Big x(s);
x.modk(k);
v.pb(std::move(x));
}
show(v);
Big b;
Big mk(string(k, '9'));
show(mk);
Big b2;
using namespace __gnu_pbds;
vector<gp_hash_table<ll, int>> to;
vector<int> res;
to.eb();
res.pb(0);
// auto add_vec = [&](const vector<ll>& v) {
// int cur = 0;
// for (auto x : v) {
// auto it = to[cur].find(x);
// if (it != to[cur].end()) {
// cur = it->second;
// } else {
// int ccur = to.size();
// to.eb();
// res.pb(0);
// to[cur][x] = ccur;
// cur = ccur;
// }
// }
// res[cur]++;
// };
// auto ask_vec = [&](const vector<ll>& v) {
// int cur = 0;
// for (auto x : v) {
// auto it = to[cur].find(x);
// if (it != to[cur].end()) {
// cur = it->second;
// } else {
// return 0;
// }
// }
// return res[cur];
// };
vector<bool> is0;
for (const auto& x : v) {
is0.pb(count(x.v.begin(), x.v.end(), 0) == x.v.size());
}
int ans = 0;
for (int i = 0; i < n; ++i) {
if (!is0[i]) continue;
for (int j = 0; j <= i; ++j) {
if (!is0[j]) continue;
for (int k = 0; k <= j; ++k) {
ans += is0[k];
}
}
}
cerr << (double)(clock() - start_time) / CLOCKS_PER_SEC << endl;
vector<array<uint32_t, P>> rems(n);
for (int i = 0; i < n; ++i) {
for (int pi = 0; pi < P; ++pi) {
rems[i][pi] = v[i].modint(primes[pi]);
}
}
array<uint32_t, P> remk;
for (int pi = 0; pi < P; ++pi) {
remk[pi] = mk.modint(primes[pi]);
}
for (int i = 0; i < n; ++i) {
cnts[rems[i]]++;
// add_vec(v[i].v);
for (int j = i; j < n; ++j) {
// b.v.assign(v[i].v.begin(), v[i].v.end());
// b.minus = false;
// b += v[j];
// b.modk(k);
// b2.v.assign(mk.v.begin(), mk.v.end());
// b2.minus = false;
// b2 -= b;
// b2.modk(k);
// b2.norm();
array<uint32_t, P> rem = remk;
for (int t = 0; t < P; ++t) {
if (rem[t] < rems[i][t]) rem[t] += primes[t];
rem[t] -= rems[i][t];
if (rem[t] < rems[j][t]) rem[t] += primes[t];
rem[t] -= rems[j][t];
}
{
auto it = cnts.find(rem);
if (it != cnts.end())
ans += it->second;
}
show(rems[i], rems[j], rem);
for (int t = 0; t < P; ++t) {
rem[t] += remk[t];
if (rem[t] >= primes[t]) rem[t] -= primes[t];
}
show(rems[i], rems[j], rem);
{
auto it = cnts.find(rem);
if (it != cnts.end())
ans += it->second;
}
// ans += ask_vec(b2.v);
// {
// array<int, 10> rems;
// for (int pi = 0; pi < 10; ++pi) {
// rems[pi] = b2.modint(primes[pi]);
// }
// auto it = cnts.find(rems);
// if (it != cnts.end())
// ans += it->second;
// }
}
}
cout << ans << '\n';
cerr << (double)(clock() - start_time) / CLOCKS_PER_SEC << endl;
return 0;
}