#include <bits/stdc++.h>
//#include <atcoder/all>
//using namespace atcoder;
using namespace std;

#pragma region Macros
using ll = long long;
#define int ll
using pii = pair<int, int>;
using tiii = tuple<int, int, int>;
template<class T = ll> using V = vector<T>;
template<class T = ll> using VV = V<V<T>>;
#define IOS\
    ios::sync_with_stdio(false);\
    cin.tie(0);\
    cout.tie(0);
#define FOR(i,l,r) for(ll i=(l);i<(r);++i)
#define REP(i,n) FOR(i,0,n)
#define REPS(i,n) FOR(i,1,n+1)
#define RFOR(i,l,r) for(ll i=(l);i>=(r);--i)
#define RREP(i,n) RFOR(i,n-1,0)
#define RREPS(i,n) RFOR(i,n,1)
#define mp make_pair
#define mt make_tuple
#define pb push_back
#define eb emplace_back
#define all(x) (x).begin(),(x).end()
#define SORT(name) sort(name.begin(), name.end())
#define RSORT(name)\
    SORT(name);\
    reverse(all(name));
#define ZERO(p) memset(p, 0, sizeof(p))
#define MINUS(p) memset(p, -1, sizeof(p))
inline void Yes(bool b = true) {cout << (b ? "Yes" : "No") << '\n';}
inline void YES(bool b = true) {cout << (b ? "YES" : "NO") << '\n';}
template <class T> inline void print(T x){ cout << x << '\n';}
template<typename T1,typename T2> inline void chmin(T1 &a, T2 b){ if(a > b) a = b; }
template<typename T1,typename T2> inline void chmax(T1 &a, T2 b){ if(a < b) a = b; }

const ll LLINF = (1LL<<60);
const int INF = (1LL<<30);
const double DINF = std::numeric_limits<double>::infinity();
#pragma endregion

#if 1
#  define DBG(fmt, ...) printf(fmt, ##__VA_ARGS__)
#else
#  define DBG(fmt, ...)
#endif

#define ME

#if defined(ME)
// 自動で余りを取ってくれる整数型
// 割り算や負の数による初期化も可能
const int MOD = 998244353;
template< int mod >
struct ModInt {
    int x;
    ModInt() : x(0) {}
    ModInt(int64_t y) : x(y >= 0 ? y % mod : (mod - (-y) % mod) % mod) {}
    ModInt &operator+=(const ModInt &p) {
        if((x += p.x) >= mod) x -= mod;
        return *this;
    }
    ModInt &operator-=(const ModInt &p) {
        if((x += mod - p.x) >= mod) x -= mod;
        return *this;
    }
    ModInt &operator*=(const ModInt &p) {
        x = (int) (1LL * x * p.x % mod);
        return *this;
    }
    ModInt &operator/=(const ModInt &p) {
        *this *= p.inverse();
        return *this;
    }
    ModInt operator-() const { return ModInt(-x); }
    ModInt operator+(const ModInt &p) const { return ModInt(*this) += p; }
    ModInt operator-(const ModInt &p) const { return ModInt(*this) -= p; }
    ModInt operator*(const ModInt &p) const { return ModInt(*this) *= p; }
    ModInt operator/(const ModInt &p) const { return ModInt(*this) /= p; }
    bool operator==(const ModInt &p) const { return x == p.x; }
    bool operator!=(const ModInt &p) const { return x != p.x; }
    ModInt inverse() const {
        int a = x, b = mod, u = 1, v = 0, t;
        while(b > 0) {
            t = a / b;
            swap(a -= t * b, b);
            swap(u -= t * v, v);
        }
        return ModInt(u);
    }
    ModInt pow(int64_t n) const {
        ModInt ret(1), mul(x);
        while(n > 0) {
            if(n & 1) ret *= mul;
            mul *= mul;
            n >>= 1;
        }
        return ret;
    }
    friend ostream &operator<<(ostream &os, const ModInt &p) {
        return os << p.x;
    }
    friend istream &operator>>(istream &is, ModInt &a) {
        int64_t t;
        is >> t;
        a = ModInt< mod >(t);
        return (is);
    }
    static int get_mod() { return mod; }
};
using mint = ModInt< MOD >;
#else
using mint = modint998244353;
#endif

vector<mint> con_one; // 1 が連続して i 回出てくる十進数
vector<mint> ten;     // 10^i

#if !defined(ME)
struct SegVal {
    mint val;     // 値
    int digit;      // 桁数
    SegVal() : val(mint(0)), digit(0) {}
    SegVal(mint a, int b) : val(a), digit(b) {}
};

SegVal e() {
    return SegVal();
}
int id() {
    return -1;
}
SegVal op(SegVal l, SegVal r) {
    return SegVal((l.val * ten[r.digit]) + r.val, l.digit + r.digit);
}
SegVal mapping(int r, SegVal l) {
    if(r == id()) { return l; }
    return SegVal(con_one[l.digit] * mint(r), l.digit);
}
int composition(int l, int r) {
    // 今回は新しい値を適用すればよい
    if(l == id()) { return r; }     // ただし恒等写像なら更新しない
    return l;
}
#else
// Update: 区間に値をセット
// Query:  区間を十進数で表した時の数字 % MOD を得る
class SetAndDecimal {
public:
    struct SegVal {
        mint val;     // 値
        int digit;    // 桁数
        SegVal() : val(mint(0)), digit(0) {}
        SegVal(mint a, int b) : val(a), digit(b) {}
    };
    // 型1(モノイド) : 扱う各データの型
    using t1 = SegVal;
    // 型2(モノイド) : mapping で作用させる値の型
    using t2 = int;

    // 単位元(=e) : t1 のある値に対して演算しても値が変わらないもの
    // 加算の場合で言う 0, min でいう場合の LLINF
    static t1 e() {
        return SegVal();
    }
    // 恒等写像 : 写像における処理(mapping)を行っても
    // セグ木の値(t1)が変わらないときのもの(t2)
    static t2 id() {
        // 今回はありえない値ならなんでもいい
        return -1;
    }
    // セグ木の値 t1 どうしの演算。区間取得で得たい値の演算を書く
    static t1 op(const t1& l, const t1& r) {
        return t1((l.val * ten[r.digit]) + r.val, l.digit + r.digit);
    }
    // 写像における操作。
    // セグ木の値 l に対して lazy の値 r を反映させる処理
    static t1 mapping(const t1& l, const t2& r) {
        if(r == id()) { return l; }
        // [lb, ub) の範囲をある値 x のみにしたいので
        // t1 の各桁すべてを x にするように書く
        return t1(con_one[l.digit] * mint(r), l.digit);
    }
    // 合成。lazy の値 l に対して r を更に適用したときの処理を記述する
    static t2 composition(const t2& l, const t2& r) {
        // 今回は新しい値を適用すればよい
        if(r == id()) { return l; }     // ただし恒等写像なら更新しない
        return r;
    }
};

template <typename M>
class LazySegmentTree {
    using T1 = typename M::t1;
    using T2 = typename M::t2;

public:
    LazySegmentTree() {}
    LazySegmentTree(int n) { Init(n); }
    void Init(int n) {
        m_N = Size(n);
        // 単位元および恒等写像で初期化
        m_Data.assign(m_N * 2, M::e());
        m_Lazy.assign(m_N * 2, M::id());
    }

    void Set(int i, T1 val) {
        m_Data[m_N + i] = val;
    }

    void Build() {
        for (int i = m_N - 1; i >= 1; i--) {
            m_Data[i] = M::op(m_Data[i * 2], m_Data[i * 2 + 1]);
        }
    }

    // 区間 [l, r) に対して値 val に対する操作を行います
    // 0-indexed
    void Update(int l, int r, T2 val) {
        UpdateImpl(l, r, 1, 0, m_N, val);
    }
    // 区間 [l, r) に対して値取得を実行します
    // 0-indexed
    T1 Query(int l, int r) {
        return QueryImpl(l, r, 1, 0, m_N);
    }

private:
    // n (配列サイズ) を 2 べきに揃える
    int Size(int n) {
        int res = 1;
        while (res < n) { res <<= 1; }
        return res;
    }
    // セグ木のノード node 以下で管理されている区間に対して遅延評価を伝搬し、
    // セグ木の値(T1) を更新します
    void Propagate(int node) {
        if (m_Lazy[node] == M::id()) { return; }    // 空(恒等写像)ならやることなし
        if (node < m_N) {
            // node が m_N より小さい -> node は長さ 2 以上の範囲を管理している
            // (自分より下にもノードがある)
            // よって自分より下のノードの lazy へ遅延を伝搬させておく
            m_Lazy[node * 2] = M::composition(m_Lazy[node * 2], m_Lazy[node]);
            m_Lazy[node * 2 + 1] = M::composition(m_Lazy[node * 2 + 1], m_Lazy[node]);
        }
        m_Data[node] = M::mapping(m_Data[node], m_Lazy[node]);
        m_Lazy[node] = M::id(); // 遅延評価の値を伝搬し終えたのでクリア
    }
    // 区間 [l,r) の処理結果を返すために、
    // そのうち m_Data のインデックス node で管理される区間 [lb, ub)
    // に対して処理します O(log N)
    T1 QueryImpl(int l, int r, int node, int lb, int ub) {
        if (ub <= l || r <= lb) { return M::e(); }  // node は [l, r) の範囲外
        if (l <= lb && ub <= r) {
            // node の区間は完全に包含されているのでこの区間の値を返せば良い
            // lazy の値がまだ処理されていないかもしれないので mapping しておく
            return M::mapping(m_Data[node], m_Lazy[node]);
        }
        Propagate(node);    // node で管理されている区間の遅延評価を終わらせておく
        // node 以下のより細かい単位の結果をみて、それの演算結果を返す
        int c = (lb + ub) / 2;
        return M::op(QueryImpl(l, r, node * 2, lb, c), QueryImpl(l, r, node * 2 + 1, c, ub));
    }
    // 区間 [l, r) に対して lazy な値 val を演算します O(log N)
    // そのうち m_Data のインデックス node で管理される区間 [lb, ub) に対して処理します
    // 0-indexed
    void UpdateImpl(int l, int r, int node, int lb, int ub, T2 val) {
        if (ub <= l || r <= lb) { return; } // node は [l, r) の範囲外
        if (l <= lb && ub <= r) {
            // node の区間は完全に包含されているので node の管理範囲全体に処理を行えば良い
            m_Lazy[node] = M::composition(m_Lazy[node], val);
            return;
        }
        Propagate(node);    // node で管理されている区間の遅延評価を終わらせておく
        // node の管理区間のちょうど半分で割って各区間に lazy の更新を行き渡らせる
        int c = (lb + ub) / 2;
        UpdateImpl(l, r, node * 2, lb, c, val);
        UpdateImpl(l, r, node * 2 + 1, c, ub, val);
        // この頂点が管理する範囲の値を更新
        // この時点で再起によって 1 つ下までの m_Data と m_Lazy の値は求まっているので、
        // それらを使って node の m_Data の値を処理する
        m_Data[node] = M::op(M::mapping(m_Data[node * 2], m_Lazy[node * 2])
                        , M::mapping(m_Data[node * 2 + 1], m_Lazy[node * 2 + 1]));
    }

private:
    int m_N;      // セグ木で扱う要素数
    vector<T1> m_Data;  // セグ木の値を保持する vector
    vector<T2> m_Lazy;  // 遅延評価用 vector
};
#endif

const int MAX_N = 200010;

signed main() {
    IOS;

    int N, Q;
    cin >> N >> Q;

    // con_one, ten の作成
    con_one.assign(N + 10, mint(0));
    ten.assign(N + 10, mint(1));
    for(int i = 1; i < N + 10; ++i) {
        con_one[i] = (con_one[i - 1] * mint(10)) + mint(1);
        ten[i] = ten[i - 1] * mint(10);
    }

#if defined(ME)
    LazySegmentTree<SetAndDecimal> lst(N);
    REP(i, N) { lst.Set(i, SetAndDecimal::SegVal(1, 1)); }
    lst.Build();
    REP(i, Q) {
        int l, r, d;
        cin >> l >> r >> d;
        l--;
        lst.Update(l, r, d);
        auto ans = lst.Query(0, N);
        print(ans.val);
    }

#else
    lazy_segtree<SegVal, op, e, int, mapping, composition, id> lst(N);
    REP(i, N) { lst.set(i, SegVal(1, 1)); }
    //lst.apply(0, N, 1);
    REP(i, Q) {
        int l, r, d;
        cin >> l >> r >> d;
        l--;
        lst.apply(l, r, d);
        auto ans = lst.prod(0, N);
        print(ans.val);
    }
#endif

    return 0;
}


Measured memory_bytes: 18784