#pragma GCC optimize("Ofast")
#define _USE_MATH_DEFINES
#include "bits/stdc++.h"

using namespace std;

using u8 = uint8_t;
using u16 = uint16_t;
using u32 = uint32_t;
using u64 = uint64_t;
using i8 = int8_t;
using i16 = int16_t;
using i32 = int32_t;
using i64 = int64_t;

using vi = vector<int>;

constexpr char newl = '\n';
constexpr double eps = 1e-10;

#define FOR(i,a,b) for (int i = (a); i < (b); i++)
#define F0R(i,b) FOR(i,0,b)
#define RFO(i,a,b) for (int i = ((b)-1); i >=(a); i--)
#define RF0(i,b) RFO(i,0,b)
#define fi first
#define se second
#define show(x) cout << #x << " = " << x << '\n';
#define rng(a) a.begin(),a.end()
#define rrng(a) a.rbegin(),a.rend()
#define sz(x) (int)(x).size()
#define YesNo {cout<<"Yes";}else{cout<<"No";}
#define YESNO {cout<<"YES";}else{cout<<"NO";}
#define v(T) vector<T>

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; }
template<class T> bool lcmp(const pair<T, T>& l, const pair<T, T>& r) {
  return l.first < r.first;
}
template<class T> istream& operator>>(istream& i, v(T)& v) {
  F0R(j, sz(v)) i >> v[j];
  return i;
}
template<class A, class B> istream& operator>>(istream& i, pair<A, B>& p) {
  return i >> p.first >> p.second;
}
template<class A, class B, class C> istream& operator>>(istream& i, tuple<A, B, C>& t) {
  return i >> get<0>(t) >> get<1>(t) >> get<2>(t);
}
template<class T> ostream& operator<<(ostream& o, const vector<T>& v) {
  F0R(i, v.size()) {
    o << v[i] << ' ';
  }
  o << newl;
  return o;
}
template<class T1, class T2> ostream& operator<<(ostream& o, const map<T1, T2>& m) {
  for (auto& p : m) {
    o << p.first << ": " << p.second << newl;
  }
  o << newl;
  return o;
}

#if 1

template<class Element, class Lazy>
class SegmentTree {

  // TODO: std::function は重いらしいのでやめる
  // https://twitter.com/satanic0258/status/1049041099115782144

  using A = function<Element(const Element&, const Element&)>;
  using B = function<void(Element&, const Lazy&, int elementCount)>;
  using C = function<void(Lazy&, const Lazy&)>;

  using ECallback = function<void(const Element&, const Lazy&)>;

  // index -> parent index
  constexpr int parent(int v) {
    return ((v + 1) >> 1) - 1;
  }
  constexpr bool hasChild(int v) {
    return v <= size - 2;
  }
  constexpr int lchild(int v) {
    return ((v + 1) << 1) - 1;
  }
  constexpr int rchild(int v) {
    return (v + 1) << 1;
  }

public:

  /// <summary>
  /// 
  /// </summary>
  /// <param name="size"></param>
  /// <param name="mergeElements">要素2つを結合</param>
  /// <param name="applyLazyToElement">要素に遅延評価分を適用</param>
  /// <param name="mergeLazies">遅延評価2つを結合</param>
  SegmentTree(int size, A mergeElements, B applyLazyToElement, C mergeLazies) :
    mergeTT(mergeElements), mergeTE(applyLazyToElement), mergeEE(mergeLazies) {

    int n = 1;
    while (n < size) n <<= 1;

    this->size = n;

    int segCount = n * 2 - 1;

    es = vector<Element>(segCount);

    ls = vector<Lazy>(segCount);

    lazyFlag = vector<bool>(segCount, false);

    lefts = vector<int>(segCount);
    rights = vector<int>(segCount);
    mp = vector<int>(segCount);

    for (int i = 0, l = 0, w = n; i < segCount; i++) {
      lefts[i] = l;
      rights[i] = lefts[i] + w;
      mp[i] = lefts[i] + (w >> 1);

      if (rights[i] >= n) {
        w >>= 1; l = 0;
      }
      else {
        l += w;
      }
    }
  }

  // 大量のデータをセットし、全てのノードの再計算を行う。O(N)
  void Set(const vector<Element>& vs) {
    int index = 0;
    while (hasChild(index)) index = lchild(index);

    F0R(i, min<int>(vs.size(), this->size)) {
      es[index++] = vs[i];
    }

    recalc();
  }

  void Set(const Lazy& value, int left, int right, int segIndex = 0) {

    //cout << "Set " << "[" << left << ", " << right << ") si=" << segIndex << newl;

    if (left <= lefts[segIndex] && rights[segIndex] <= right) {
      add(segIndex, value);
      resolve(segIndex);
    }
    else {
      resolve(segIndex);

      auto l = lchild(segIndex), r = rchild(segIndex);
      if (left < mp[segIndex]) {
        Set(value, left, right, l);
      }
      else {
        resolve(l);
      }
      if (mp[segIndex] < right) {
        Set(value, left, right, r);
      }
      else {
        resolve(r);
      }

      es[segIndex] = mergeTT(es[l], es[r]);
    }
  }

  Element Get(int left, int right, int segIndex = 0) {

    if (right <= left) return Element();

    resolve(segIndex);
    if (left <= lefts[segIndex] && rights[segIndex] <= right) {
      return es[segIndex];
    }
    else {
      if (left < mp[segIndex]) {
        if (mp[segIndex] < right) {
          return mergeTT(Get(left, right, lchild(segIndex)), Get(left, right, rchild(segIndex)));
        }
        return Get(left, right, lchild(segIndex));
      }
      else if (mp[segIndex] < right) {
        return Get(left, right, rchild(segIndex));
      }
    }
  }

  void Print(ECallback c) {
    F0R(i, es.size()) {
      c(es[i], ls[i]);

      if (((i + 2) & (i + 1)) == 0) {
        cout << "." << newl;
      }
    }
  }

protected:

  void resolve(int segIndex) {
    if (!lazyFlag[segIndex]) return;

    mergeTE(es[segIndex], ls[segIndex], rights[segIndex] - lefts[segIndex]);

    if (hasChild(segIndex)) {
      auto l = lchild(segIndex), r = rchild(segIndex);
      add(l, ls[segIndex]);
      add(r, ls[segIndex]);
    }

    ls[segIndex] = Lazy();
    lazyFlag[segIndex] = false;
  }

  // 遅延データを追加
  void add(int segIndex, const Lazy& value) {

    if (lazyFlag[segIndex]) {
      mergeEE(ls[segIndex], value);
    }
    else {
      //cout << "set index" << segIndex << newl;
      ls[segIndex] = value;
      lazyFlag[segIndex] = true;
    }
  }

  // 指定したノードと全ての子を計算。一度に大量のデータの変更があったときのみ
  void recalc(int segIndex = 0) {
    if (hasChild(segIndex)) {
      recalc(lchild(segIndex));
      recalc(rchild(segIndex));
      es[segIndex] = mergeTT(es[lchild(segIndex)], es[rchild(segIndex)]);
    }
  }

  A mergeTT;
  B mergeTE;
  C mergeEE;

  // 要素ごとのデータ
  vector<Element> es;

  // 要素ごとの遅延データ
  vector<Lazy> ls;

  // 遅延データがあるか
  vector<bool> lazyFlag;

  // 区間 [l, r) とその中間
  vector<int> lefts, rights, mp;

  // 区間のサイズ = 子を持たないエレメント数
  int size;
};

constexpr int MOD = 998244353;
template<int MOD>
struct Modint {
  long long val;
  constexpr Modint(long long v = 0) noexcept : val(v% MOD) {
    if (val < 0) val += MOD;
  }
  constexpr int getmod() { return MOD; }
  constexpr Modint operator - () const noexcept {
    return val ? MOD - val : 0;
  }
  constexpr Modint operator + (const Modint& r) const noexcept { return Modint(*this) += r; }
  constexpr Modint operator - (const Modint& r) const noexcept { return Modint(*this) -= r; }
  constexpr Modint operator * (const Modint& r) const noexcept { return Modint(*this) *= r; }
  constexpr Modint& operator += (const Modint& r) noexcept {
    val += r.val;
    if (val >= MOD) val -= MOD;
    return *this;
  }
  constexpr Modint& operator -= (const Modint& r) noexcept {
    val -= r.val;
    if (val < 0) val += MOD;
    return *this;
  }
  constexpr Modint& operator *= (const Modint& r) noexcept {
    val = val * r.val % MOD;
    return *this;
  }
  constexpr bool operator == (const Modint& r) const noexcept {
    return this->val == r.val;
  }
  constexpr bool operator != (const Modint& r) const noexcept {
    return this->val != r.val;
  }
  friend constexpr ostream& operator << (ostream& os, const Modint<MOD>& x) noexcept {
    return os << x.val;
  }
  friend constexpr istream& operator >> (istream& is, Modint<MOD>& x) noexcept {
    return is >> x.val;
  }

  constexpr Modint<MOD> pow(long long n) const noexcept {
    if (!n) return 1;
    auto t = pow(n >> 1);
    t *= t;
    if (n & 1) t *= *this;
    return t;
  }
  friend constexpr Modint<MOD> modpow(const Modint<MOD>& a, long long n) noexcept {
    return a.pow(n);
  }

  friend constexpr Modint<MOD> Ncr(Modint<MOD> a, int n) {
    Modint<MOD> p = 1, q = 1;

    while (n > 0) {
      p *= a;
      a -= 1;
      q *= n--;
    }

    return p / q;
  }

  // for prime mod
  constexpr Modint inv() const { return pow(MOD - 2); }
  constexpr Modint& operator /= (const Modint& r) noexcept {
    long long a = r.val, b = MOD, u = 1, v = 0;
    while (b) {
      long long t = a / b;
      a -= t * b; swap(a, b);
      u -= t * v; swap(u, v);
    }
    val = val * u % MOD;
    if (val < 0) val += MOD;
    return *this;
  }
  constexpr Modint operator / (const Modint& r) const noexcept { return Modint(*this) /= r; }
};
using mint = Modint<MOD>;


// INSERT ABOVE HERE
signed main() {
  cin.tie(0);
  ios_base::sync_with_stdio(false);

  vector<mint> t(200001), o(200001);
  t[0] = 1; o[0] = 0;
  F0R(i, 200000) {
    t[i + 1] = t[i] * 10;
    o[i + 1] = o[i] * 10 + 1;
  }

  struct Elem {
    mint Sum = 0;
    int Keta = 1;
  };
  struct Lazy {
    i64 Set = 0;
  };

  auto c1 = [&t](const Elem& l, const Elem& r) -> Elem {
    Elem o;
    o.Sum = l.Sum + t[l.Keta] * r.Sum;
    o.Keta = l.Keta + r.Keta;
    return o;
  };
  auto c2 = [&o](Elem& l, const Lazy& r, int elementCount) -> void {
    //show(l.Sum);
    l.Sum = o[l.Keta] * r.Set;
    //show(l.Sum);
  };
  auto c3 = [](Lazy& l, const Lazy& r) -> void {
    l.Set = r.Set;
  };
  auto qSet = [](i64 value) -> Lazy {
    Lazy l;
    l.Set = value;
    return l;
  };

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

  int n = 1;
  SegmentTree<Elem, Lazy> st(N, c1, c2, c3);
  vector<Elem> e(N);
  F0R(i, N) {
    e[i] = { 1,1 };
  }
  st.Set(e);
  F0R(i, 7) {
    //cout << st.Get(0, i+1).Sum << " " << st.Get(0, i+1).Keta << newl;
  }
  //cout << st.Get(0, N).Sum << newl;
  //st.Set(qSet(2), 0, 1);
  //cout << st.Get(0, N).Sum << newl;


  F0R(i, Q) {
    int l, r, d;
    cin >> l >> r >> d;
    st.Set(qSet(d), N - r, N - l + 1);
    cout << st.Get(0, N).Sum << newl;
  }
}
#endif


Measured memory_bytes: 28028