#include <bits/stdc++.h>

using namespace std;

#define REP(i, n) for (int i=0; i<(n); ++i)
#define RREP(i, n) for (int i=(int)(n)-1; i>=0; --i)
#define FOR(i, a, n) for (int i=(a); i<(n); ++i)
#define RFOR(i, a, n) for (int i=(int)(n)-1; i>=(a); --i)

#define SZ(x) ((int)(x).size())
#define ALL(x) (x).begin(),(x).end()

#define DUMP(x) cerr<<#x<<" = "<<(x)<<endl
#define DEBUG(x) cerr<<#x<<" = "<<(x)<<" (L"<<__LINE__<<")"<<endl;

template<class T>
ostream &operator<<(ostream &os, const vector<T> &v) {
  os << "[";
  REP(i, SZ(v)) {
    if (i) os << ", ";
    os << v[i];
  }
  return os << "]";
}

template<class T, class U>
ostream &operator<<(ostream &os, const pair<T, U> &p) {
  return os << "(" << p.first << " " << p.second << ")";
}

template<class T>
bool chmax(T &a, const T &b) {
  if (a < b) {
    a = b;
    return true;
  }
  return false;
}

template<class T>
bool chmin(T &a, const T &b) {
  if (b < a) {
    a = b;
    return true;
  }
  return false;
}

using ll = long long;
using ull = unsigned long long;
using ld = long double;
using P = pair<int, int>;
using vi = vector<int>;
using vll = vector<ll>;
using vvi = vector<vi>;
using vvll = vector<vll>;

const ll MOD = 998244353;
const int INF = INT_MAX / 2;
const ll LINF = LLONG_MAX / 2;
const ld eps = 1e-9;

template<typename M, typename OM = M>
struct LazySegmentTree {

  /**
  * @brief コンストラクタ. O(n)
  * @param[in] n 遅延セグ木のサイズ.
  * @param[in] f 要素モノイドの演算.
  * @param[in] g 要素に作用素を作用させる演算.
  * @param[in] h 作用素モノイドの演算.
  * @param[in] e 要素モノイドの単位元.
  * @param[in] oe 作用素モノイドの単位元.
  * @details 使い方
  *   e.g. Range Add Range Sum
  *   LazySegmentTree<int> segt(
  *            n,
  *            [](int a,int b){ return a+b; },
  *            [](int a,int b,int w){ return a + b*w; },
  *            [](int a,int b){ return a+b; },
  *            0, 0);
  *               // 全て単位元で初期化される.
  */
  LazySegmentTree(
      int n,
      const function<M(M, M)> &f,
      const function<M(M, OM, int)> &g,
      const function<OM(OM, OM)> &h,
      const M &e, const OM &oe
  ) : n(n), f(f), g(g), h(h), e(e), oe(oe) {
    sz = 1;
    while (sz < n) sz <<= 1;
    data.assign(2 * sz, e);
    lazy.assign(2 * sz, oe);
  }

  /**
  * @brief 全体に初期値を入れる. O(n)
  * @param[in] v 要素モノイドのvector. 初期化する.
  * @details 使い方
  *   segt.build(vector<int>(n, INF));
  */
  void build(const vector<M> &v) {
    assert(v.size() <= n);
    for (int i = 0; i < v.size(); ++i) {
      data[i + sz] = v[i];
    }
    for (int i = sz - 1; i > 0; --i) {
      data[i] = f(data[2 * i], data[2 * i + 1]);
    }
  }

  /**
  * @brief 指定した区間に作用素x を作用させる. O(log n)
  * @param[in] l, r 区間[l, r) に作用させる.
  * @param[in] x 作用素モノイドの元.
  * @details 使い方
  *   e.g. Range Update
  *   int l, r, x; // 区間[l, r) をx に更新したい.
  *   segt.update(l, r, x);
  */
  void update(int a, int b, const OM &x) {
    update(a, b, x, 1, 0, sz);
  }

  /**
  * @brief 指定した区間に取得クエリを実行する. O(log n)
  * @param[in] l, r 区間[l, r) を取得する.
  * @return 取得した値.
  * @details 使い方
  *   e.g. Range Minimum
  *   int l, r; // 区間[l, r) のminを取得したい.
  *   cout << segt.query(l, r) << endl;
  */
  M query(int a, int b) {
    return query(a, b, 1, 0, sz);
  }

  /**
  * @brief 指定したindexの要素を取得. O(1)
  * @param[in] i 取得したい要素のindex
  * @return 取得した値.
  */
  M operator[](int i) {
    return query(i, i + 1);
  }

  /**
  * @brief vector みたいに出力.
  */
  friend ostream &operator<<(ostream &os, LazySegmentTree &s) {
    os << "[";
    for (int i = 0; i < s.n; ++i) {
      if (i) os << " ";
      os << s[i];
    }
    return os << "]";
  }


  int n, sz;
  vector<M> data;
  vector<OM> lazy;
  const function<M(M, M)> f;
  const function<M(M, OM, int)> g;
  const function<OM(OM, OM)> h;
  const M e;
  const OM oe;

  void propagate(int k, int len) {
    if (lazy[k] == oe) return;
    if (k < sz) {
      lazy[2 * k] = h(lazy[2 * k], lazy[k]);
      lazy[2 * k + 1] = h(lazy[2 * k + 1], lazy[k]);
    }
    data[k] = g(data[k], lazy[k], len);
    lazy[k] = oe;
  }

  void update(int a, int b, const OM &x, int k, int l, int r) {
    propagate(k, r - l);
    if (r <= a or b <= l) return;
    else if (a <= l and r <= b) {
      lazy[k] = h(lazy[k], x);
      propagate(k, r - l);
    } else {
      update(a, b, x, 2 * k, l, (l + r) >> 1);
      update(a, b, x, 2 * k + 1, (l + r) >> 1, r);
      data[k] = f(data[2 * k], data[2 * k + 1]);
    }
  }

  M query(int a, int b, int k, int l, int r) {
    propagate(k, r - l);
    if (r <= a or b <= l) return e;
    else if (a <= l and r <= b) return data[k];
    else
      return f(
          query(a, b, 2 * k, l, (l + r) >> 1),
          query(a, b, 2 * k + 1, (l + r) >> 1, r));
  }
};

ll powm(ll a, ll n, ll m) {
  ll ret = 1;
  while (n > 0) {
    if (n & 1) (ret *= a) %= m;
    (a *= a) %= m;
    n >>= 1;
  }
  return ret;
}

ll invm(ll a, ll m) {
  return powm(a, m-2, m);
}

const ll inv9 = invm(9, MOD);
int main() {
  cin.tie(0);
  ios::sync_with_stdio(false);
  cout << fixed << setprecision(10);

  int n, q;
  cin >> n >> q;

  using P = pair<ll, ll>;
  LazySegmentTree<P, P> st(
      n,
      [](P a, P b) {
        return make_pair((a.first * powm(10, b.second, MOD) % MOD + b.first) % MOD, a.second + b.second);
        },
      [](P a, P b, int w) {
        ll val = b.first * ((powm(10, w, MOD) + MOD - 1) % MOD) % MOD * inv9 % MOD;
        return make_pair(val, w);
        },
      [](P a, P b) { return b; },
      {0, 0},
      {-INF, -INF}
  );
  vector<P> v(n, {1, 1});
  st.build(v);

  REP(i, q) {
    int l, r, d;
    cin >> l >> r >> d;
    l--;
    st.update(l, r, {d, r - l});
    cout << st.query(0, n).first << endl;
  }

  ll ans = 0;

  return 0;
}

Measured memory_bytes: 22720