#include<bits/stdc++.h>
using namespace std;
using ll = long long;
const ll N = 2e5 + 5, M = 1e9 + 7; // N:数组最大长度 M:取模模数(防止数值溢出)
ll a[N]; // 存储输入的数组元素
vector<ll>pre_f(10, 0); // DP状态数组:记录「当前元素之后的子数组」能组合出的结果(模10)的频次
vector<ll>cur_f(10, 0); // 临时数组:记录「当前元素与后续子数组组合」后能得到的结果(模10)的频次
/*
算法核心思想:采用逆序动态规划(DP),从后往前遍历数组,通过pre_f记录 “后续子数组的组合结果频次”,cur_f记录 “当前元素与后续组合的结果频次”,逐步推导所有元素组合(加 / 乘)后的模 10 频次;
关键步骤注释:
特殊处理n=1:无后续元素可组合,直接处理单个元素的情况;
数组预处理:所有元素取模 10,等价简化计算;
DP 初始化:最后一个元素无后续元素,仅自身作为初始状态;
逆序循环:遍历后续所有可能结果,分别计算 “加”“乘” 组合的频次,并用模数M防止数值溢出;
状态更新:将当前组合结果传递给前一个元素,作为其 “后续子数组结果”,每一个 cur_f 数组中某一元素(方案数)的状态只由当前元素 a[i] 与上一轮 pre_f 数组的每个非0元素有关
*/
int main() {
ios::sync_with_stdio(0), cin.tie(0); // 关闭同步流,加速cin/cout输入输出
ll n;
cin >> n;
// 特殊情况处理:数组长度为1时(无后续元素可组合)
if (n == 1) {
ll tmp;
cin >> tmp;
if (tmp < 10) {
cur_f[tmp] = 1;
}
for (auto t : cur_f) {
cout << t << " ";
}
return 0;
}
// 步骤1:输入数组并预处理(所有元素取模10,简化后续加/乘计算)
for (ll i = 1; i <= n; i++) {
cin >> a[i];
a[i] %= 10; // 模10后结果等价,减少计算量
}
// 步骤2:DP初始化(最后一个元素是后续子数组的起点,初始时仅自身可作为组合的右半部分)
pre_f[a[n]] = 1; // 初始状态:最后一个元素的模10结果频次为1
// 步骤3:逆序DP核心循环(从倒数第二个元素往前遍历,逐步计算组合结果)
// 算法思想:对于每个元素a[i],它可以和后续子数组的所有组合结果做「加」或「乘」操作
for (ll i = n - 1; i >= 1; i--) {
ll x = a[i]; // 当前处理的元素(模10后的值)
// 重置临时数组:每次处理新元素前清空,避免上一轮结果残留
cur_f.assign(10, 0);
// 遍历后续子数组能组合出的所有模10结果(0-9)
for (ll j = 0; j < 10; j++) {
if (pre_f[j] == 0) continue; // 后续子数组无法组合出j,跳过
// 组合1:当前元素 + 后续结果 → 模10,累加频次(取模防止溢出)
cur_f[(x + j) % 10] += pre_f[j];
cur_f[(x + j) % 10] %= M;
// 组合2:当前元素 * 后续结果 → 模10,累加频次(取模防止溢出)
cur_f[(x * j) % 10] += pre_f[j];
cur_f[(x * j) % 10] %= M;
}
// 更新DP状态:将当前元素组合后的结果作为「前一个元素的后续子数组结果」
pre_f = cur_f;
}
// 步骤4:输出最终结果(pre_f存储了所有元素组合后,0-9每个数字模10的频次)
for (auto t : pre_f) {
cout << t << " ";
}
return 0;
}