#include <algorithm>
#include <array>
#include <cassert>
#include <cctype>
#include <cmath>
#include <cstdint>
#include <cstdio>
#include <functional>
#include <iomanip>
#include <iostream>
#include <limits>
#include <map>
#include <numeric>
#include <optional>
#include <queue>
#include <set>
#include <string>
#include <string_view>
#include <tuple>
#include <unordered_map>
#include <unordered_set>
#include <vector>
using namespace std;

int64_t read_int() {
    int64_t ret = 0, sgn = 1;
    int ch = getchar_unlocked();
    while (isspace(ch)) { ch = getchar_unlocked(); }
    if (ch == '-') { sgn = -1; ch = getchar_unlocked(); }
    for (; isdigit(ch); ch = getchar_unlocked())
        ret = (ret * 10) + (ch - '0');
    ungetc(ch, stdin);
    return sgn * ret;
}

#define REP(i, n) for (int i = 0; i < (int)(n); ++i)
#define FOR(i, a, b) for (int i = (int)(a); i < (int)(b); ++i)
#define ALL(c) (c).begin(), (c).end()

template <typename T> inline void assign_min(T& x, const T& value) noexcept { x = min(x, value); }
template <typename T> inline void assign_max(T& x, const T& value) noexcept { x = max(x, value); }

constexpr char LF = '\n';

//-------------------------------------------------------------------------------------------------
#include <boost/operators.hpp>

// a を m で割った余り(>=0)を返す。
constexpr inline int mod(int a, int m) noexcept {
    int r = a % m;
    return r < 0 ? r + m : r;
}

// ax + by = gcd(a, b) を満たすような整数 x,y を返す。
template<int MOD>
pair<int64_t, int64_t> exgcd(int a, int b) noexcept {
    if (b == 0) return {1, 0};
    auto [x, y] = exgcd<MOD>(b, a%b);
    return {y, x-(a/b)*y};
}

// a の逆元を返す。a は MOD と互いに素でなければならない。
template<int MOD>
inline int inverse(int a) noexcept {
    return mod(exgcd<MOD>(MOD, a).second, MOD);
}

template<int MOD>
struct ModInt : private boost::operators<ModInt<MOD>> {
    int value;
    constexpr ModInt() noexcept : value(0) {}
    constexpr ModInt(int value) noexcept : value(mod(value, MOD)) {}
    constexpr ModInt<MOD>& operator+=(const ModInt<MOD>& rhs) noexcept {
        value += rhs.value;
        if (value > MOD) value -= MOD;
        return *this;
    }
    constexpr ModInt<MOD>& operator-=(const ModInt<MOD>& rhs) noexcept {
        value -= rhs.value;
        if (value < 0) value += MOD;
        return *this;
    }
    constexpr ModInt<MOD>& operator*=(const ModInt<MOD>& rhs) noexcept {
        value = ((int64_t)value * rhs.value) % MOD;
        return *this;
    }
    ModInt<MOD>& operator/=(const ModInt<MOD>& rhs) noexcept {
        return *this *= rhs.inverse();
    }
    constexpr bool operator<(const ModInt<MOD>& rhs) const noexcept {
        return value < rhs.value;
    }
    constexpr bool operator==(const ModInt<MOD>& rhs) const noexcept {
        return value == rhs.value;
    }
    ModInt<MOD> pow(int64_t n) const noexcept {
        auto a = *this;
        auto ans = ModInt<MOD>(1);
        while (n > 0) {
            if (n & 1) ans *= a;
            a *= a;
            n >>= 1;
        }
        return ans;
    }
    ModInt<MOD> inverse() const noexcept {
        return ModInt<MOD>(::inverse<MOD>(value));
    }
};
template<int MOD>
ostream& operator<<(ostream& os, const ModInt<MOD>& rhs) {
    os << rhs.value;
    return os;
}
template<int MOD>
istream& operator>>(istream& is, ModInt<MOD>& rhs) {
    int i; is >> i;
    rhs.value = mod(i, MOD);
    return is;
}

using mint = ModInt<998244353>;

int bit_ceil(int n) {
    int ret = 1;
    while (ret < n) ret *= 2;
    return ret;
}

struct LazySegmentTree {
    LazySegmentTree(int size, vector<vector<mint>>&& A):
        n(bit_ceil(size)), data(2*n-1), delayed(2*n-1, nullopt), A(A) {}

    // [lo, hi) の区間のすべての要素を update(*, value) によって更新する
    void range_update(int lo, int hi, int d) {
        range_update_(lo, hi, d, 0, 0, n);
    }

    mint query_all() {
        force(0, 0, n);
        return data[0];
    }

private:
    void range_update_(int lo, int hi, int d, int index, int left, int right) {
        force(index, left, right);
        if (hi <= left || right <= lo) { // この区間と更新区間が全く重ならないとき
            return;
        }
        if (lo <= left && right <= hi) { // この区間が更新区間に完全に包含されているとき
            data[index] = A[d][right-left];
            delayed[index] = d;
            return;
        }
        // この区間の一部が更新区間とオーバーラップしているとき
        int middle = (left + right) / 2;
        range_update_(lo, hi, d, index*2+1, left, middle);
        range_update_(lo, hi, d, index*2+2, middle, right);

        mint a = data[index*2+1];
        mint b = data[index*2+2];
        data[index] = a + b * mint(10).pow(right-middle);
        //cout << index << " " << data[index] << endl;
    }

    void force(int index, int left, int right) {
        if (!delayed[index]) return;
        if (index < n-1) {
            int middle = (left + right) / 2;
            force_child(index*2+1, *delayed[index], left, middle);
            force_child(index*2+2, *delayed[index], middle, right);
        }
        delayed[index] = nullopt;
    }

    void force_child(int index, int d, int left, int right) {
        data[index] = A[d][right-left];
        delayed[index] = d;
    }

private:
    int n;
    vector<mint> data;
    vector<optional<int>> delayed;
    vector<vector<mint>> A;
};

int main() {
    int N = read_int();
    int Q = read_int();

    int M = bit_ceil(N);
    vector A(10, vector(M+1, mint()));
    REP(i, 10) {
        A[i][0] = 0;
        FOR(j, 1, M+1) {
            A[i][j] = A[i][j-1]*10 + i;
        }
    }

    LazySegmentTree st(N, move(A));
    st.range_update(0, N, 1);

    REP(i, Q) {
        int l = read_int() - 1;
        int r = read_int() - 1;
        int d = read_int();
        st.range_update(N-r-1, N-l, d);
        cout << st.query_all() << LF;
    }
}


Measured memory_bytes: 29520