#include <bits/stdc++.h>
using namespace std;
 
#define TASK "FAKERNUM"
#define all(x) (x).begin(), (x).end()
 
typedef long long ll;
typedef pair<int, int> ii;
 
const int MAX_N = 1e5;
const int MAX_Q = 5e5;
const int MOD = (int)1e9 + 7;
 
template <class X, class Y>
bool maximize(X& x, const Y& y) {
    if (x >= y) return false;
    x = y;
    return true;
};
template <class X, class Y>
bool minimize(X& x, const Y& y) {
    if (x <= y) return false;
    x = y;
    return true;
};
 
struct Query {
    int type, u, v;
    ll x;
    Query() {};
};
 
int n, q, timer;
int tin[MAX_N + 5], tout[MAX_N + 5], par[MAX_N + 5], depth[MAX_N + 5], id[MAX_N + 5];
bool match[20][20];
ll a[MAX_N + 5];
vector<int> adj[MAX_N + 5];
vector<Query> queries;
 
void preDfs(int u, int p) {
    tin[u] = ++timer;
    id[tin[u]] = u;
    par[u] = p;
    depth[u] = depth[p] + 1;
 
    for (int v : adj[u]) {
        if (v == p) continue;
        preDfs(v, u);
    };
 
    tout[u] = timer;
};
 
bool isAncestor(int u, int v) {
    return tin[u] <= tin[v] && tout[u] >= tout[v];
};
 
bool fakerNumber(ll x) {
    string s = to_string(x);
    if (count(all(s), '3') + count(all(s), '6') != s.size()) return false;
 
    int n = s.size();
 
    for (int i = 0; i < n; i++)
        for (int j = 0; j < n; j++) match[i][j] = false;
 
    int numPalin = 0;
    for (int i = 0; i < n; i++) {
        match[i][i] = true;
        numPalin++;
    };
    for (int i = 0; i + 1 < n; i++) {
        match[i][i + 1] = s[i] == s[i + 1];
        numPalin += match[i][i + 1];
    };
 
    for (int l = n - 3; l >= 0; l--) {
        for (int r = l + 2; r < n; r++) {
            match[l][r] = match[l + 1][r - 1] && (s[l] == s[r]);
            numPalin += match[l][r];
        };
    };
 
    return (double)numPalin / (n * (n + 1) / 2) > 0.5;
};
 
namespace SUBTASK_1 {
    const int N = MAX_N;
    const int Q = 5000;
 
    ll b[MAX_N + 5];
 
    int subtreeQuery(int u) {
        int res = 0;
        for (int i = tin[u]; i <= tout[u]; i++) {
            int v = id[i];
            if (fakerNumber(b[v])) res++;
        };
        return res;
    };
 
    int pathQuery(int u, int v) {
        if (depth[u] > depth[v]) swap(u, v);
 
        int res = 0;
        while (depth[v] != depth[u]) {
            res += fakerNumber(b[v]);
            v = par[v];
        };
 
        while (u != v) {
            res += fakerNumber(b[u]);
            res += fakerNumber(b[v]);
            u = par[u], v = par[v];
        };
 
        res += fakerNumber(b[u]);
 
        return res;
    };
 
    void updatePath(int u, int v, ll val) {
        if (depth[u] > depth[v]) swap(u, v);
 
        while (depth[v] != depth[u]) {
            b[v] += val;
            v = par[v];
        };
 
        while (u != v) {
            b[u] += val;
            b[v] += val;
            u = par[u], v = par[v];
        };
 
        b[u] += val;
    };
 
    void Solve() {
        for (int u = 1; u <= n; u++) b[u] = a[u];
 
        for (const Query& query : queries) {
            int type = query.type, u = query.u;
            if (type == 1) {
                int v = query.v;
                ll x = query.x;
                updatePath(u, v, x);
            };
            if (type == 2) {
                int v = query.v;
                cout << pathQuery(u, v) << '\n';
            };
            if (type == 3) {
                cout << subtreeQuery(u) << '\n';
            };
        };
    };
};  // namespace SUBTASK_1
 
namespace SUBTASK_2 {
    const int N = MAX_N;
    const int Q = MAX_Q;
 
    int up[N + 5][18];
    int pref[N + 5], f[N + 5];
 
    bool checkSubtask() {
        for (const Query& query : queries)
            if (query.type == 1) return false;
        return true;
    };
 
    void dfs(int u) {
        up[u][0] = par[u];
        for (int i = 1; (1 << i) <= n; i++)
            up[u][i] = up[up[u][i - 1]][i - 1];
 
        f[u] = f[par[u]] + fakerNumber(a[u]);
 
        for (int v : adj[u]) {
            if (v == par[u]) continue;
            dfs(v);
        };
    };
 
    int __lca(int u, int v) {
        if (isAncestor(u, v)) return u;
        if (isAncestor(v, u)) return v;
 
        for (int i = 17; i >= 0; i--)
            if ((1 << i) <= n && !isAncestor(up[u][i], v))
                u = up[u][i];
 
        return up[u][0];
    };
 
    void Solve() {
        for (int i = 1; i <= n; i++) pref[i] = pref[i - 1] + fakerNumber(a[id[i]]);
 
        dfs(1);
 
        for (const Query& query : queries) {
            int type = query.type, u = query.u;
            if (type == 2) {
                int v = query.v;
                int lca = __lca(u, v);
                cout << f[u] + f[v] - 2 * f[lca] + fakerNumber(a[lca]) << '\n';
            };
            if (type == 3) {
                cout << pref[tout[u]] - pref[tin[u] - 1] << '\n';
            };
        };
    };
};  // namespace SUBTASK_2
 
int main() {
    ios_base::sync_with_stdio(0);
    cin.tie(0);
    if (fopen(TASK ".INP", "r")) {
        freopen(TASK ".INP", "r", stdin);
        freopen(TASK ".OUT", "w", stdout);
    };
 
    cin >> n >> q;
    for (int u = 1; u <= n; u++) {
        cin >> a[u];
    };
 
    for (int i = 1; i < n; i++) {
        int u, v;
        cin >> u >> v;
        adj[u].push_back(v);
        adj[v].push_back(u);
    };
 
    queries.resize(q);
    for (Query& query : queries) {
        cin >> query.type >> query.u;
        if (query.type == 1) cin >> query.v >> query.x;
        if (query.type == 2) cin >> query.v;
    };
 
    preDfs(1, 1);
 
    if (SUBTASK_2::checkSubtask())
        return SUBTASK_2::Solve(), 0;
    SUBTASK_1::Solve();
    // cout << endl;
};