#include <bits/stdc++.h>
using namespace std;
using ll = long long;
constexpr int P = 998244353;


//============================ ModInt ============================

template<long long MOD> struct modint {
	long long value;
	modint(const long long& n = 0) : value(n) {
		value %= MOD;
		if (value < 0) value += MOD;
	}
	modint<MOD> operator+(const modint<MOD>& right)const{modint<MOD> ret = value + right.value; return ret;}
	modint<MOD> operator-(const modint<MOD>& right)const{modint<MOD> ret = value - right.value; return ret;}
	modint<MOD> operator*(const modint<MOD>& right)const{modint<MOD> ret = value * right.value; return ret;}
	inline modint<MOD>& operator+=(modint<MOD>& right) {return *this = *this + right;}
	inline modint<MOD>& operator-=(modint<MOD>& right) {return *this = *this - right;}
	inline modint<MOD>& operator*=(modint<MOD>& right) {return *this = *this * right;}
};
template<long long MOD> modint<MOD> operator+(const long long& left, const modint<MOD>& right) {
	return right + left;
}
template<long long MOD> modint<MOD> operator-(const long long& left, const modint<MOD>& right) {
	return right * -1 + left;
}
template<long long MOD> modint<MOD> operator*(const long long& left, const modint<MOD>& right) {
	return right * left;
}

template<long long MOD> std::istream& operator>>(std::istream& is, modint<MOD>& target) {
	is >> target.value;
	target.value %= MOD;
	if (target.value < 0) target.value += MOD;
	return is;
}
template<long long MOD> inline std::ostream& operator<<(std::ostream& os, const modint<MOD>& target) {
	os << target.value;
	return os;
}

//============================ ModInt ここまで ============================


vector<modint<P>> tenpow; // tenpow[N - 1 - i] = 10^i
vector<modint<P>> sum; // tenpow を右から累積和　sum[l] - sum[r] = Σ[l,r-1]tenpow[i]


//=============================== SegTree ================================

template<typename T, typename E, typename F, typename G, typename H> struct lazy_segment_tree {
private :
	int N; // 葉の数
	std::vector<T> V; // 値配列
	std::vector<E> W; // 遅延配列
	std::vector<bool> unevaluated; // 遅延配列に入っている値がまだ値配列に作用していないかどうかのフラグ
	const F op_V; // 値配列における2項演算のラムダ式
	const G op_W; // 遅延配列における遅延伝播用のラムダ式
	const H op_V_from_W; // 遅延配列から値配列へ作用させるラムダ式
	const T id_V; // op_V の単位元
	const E id_W; // op_W の単位元

	// 遅延評価
	// i : ノードの添字　[l, r) : ノードがカバーする区間
	void lazy_evaluation(const int& i, const int& l, const int& r) {
		// 既に作用済なら何もしない
		if (!unevaluated[i]) return;

		// そうでないなら値配列へ作用させる
		V[i] = op_V_from_W(V[i], W[i], l, r);

		// 今見てるノードが葉でないなら左右の子へ伝播させて、伝播先のフラグを立てておく
		if (l + 1 < r) {
			W[i * 2] = op_W(W[i * 2], W[i]);
			W[i * 2 + 1] = op_W(W[i * 2 + 1], W[i]);
			unevaluated[i * 2] = unevaluated[i * 2 + 1] = true;
		}

		// 今のノードは作用済なので単位元を入れてフラグを倒しておく
		W[i] = id_W;
		unevaluated[i] = false;
	}

public :
	// コンストラクタ
	lazy_segment_tree(const std::vector<T>& v, const F& f, const G& g, const H& h, const T& t, const E& e)
	: op_V(f), op_W(g), op_V_from_W(h), id_V(t), id_W(e) {
		int n = 1, sz = v.size();
		while (n < sz) n *= 2;
		N = n;
		V.resize(N * 2, id_V);
		W.resize(N * 2, id_W);
		unevaluated.resize(N * 2, false);
		for (int i = 0; i < sz; i++) V[N + i] = v[i];
		for (int i = N - 1; i > 0; i--) V[i] = op_V(V[i * 2], V[i * 2 + 1]);
	}
	// コンストラクタ　サイズだけ指定して全部単位元で初期化する場合
	lazy_segment_tree(const int& sz, const F& f, const G& g, const H& h, const T& t, const E& e)
	: op_V(f), op_W(g), op_V_from_W(h), id_V(t), id_W(e) {
		int n = 1;
		while (n < sz) n *= 2;
		N = n;
		V.resize(N * 2, id_V);
		W.resize(N * 2, id_W);
		unevaluated.resize(N * 2, false);
	}

	/*	区間更新
		[L, R) の値全てに e を作用させる
		i : ノードの添字　[l, r) : ノードがカバーする区間
	*/
	void range_update(const int& L, const int& R, const E& e, const int& i = 1, const int& l = 0, int r = -1) {
		// デフォルト引数の再設定　あんま気にしない
		if (r == -1) r = N;

		// 今見てるノードが作用させたい区間から完全に外れてるとき、遅延評価だけして帰る
		if (r <= L || R <= l) {
			lazy_evaluation(i, l, r);
			return;
		}

		// 今見てるノードが作用させたい区間に完全に収まってるとき、そのノードの遅延配列に作用させればOK
		// ほんでフラグ立てて遅延評価しておく
		if (L <= l && r <= R) {
			W[i] = op_W(W[i], e);
			unevaluated[i] = true;
			lazy_evaluation(i, l, r);
			return;
		}

		// 以下、区間が中途半端に被ってる場合
		// もし今見てるノードの遅延配列が未作用なら遅延評価しておく
		if (unevaluated[i]) lazy_evaluation(i, l, r);
		// 区間の真ん中
		int mid = (l + r) / 2;
		// 左右の子へ再帰
		// なお区間が中途半端に被ってるということは今見てるノードの区間は必ず長さ2以上で、まだ子供が存在する
		range_update(L, R, e, i * 2, l, mid);
		range_update(L, R, e, i * 2 + 1, mid, r);
		// 子供への伝播が終わったので、子供の値から自分の値を更新する
		V[i] = op_V(V[i * 2], V[i * 2 + 1]);
	}

	/*	区間取得
		[L, R) の値を fold する
		i : ノードの添字　[l, r) : ノードがカバーする区間
	*/
	T range_fold(const int& L, const int& R, const int& i = 1, const int& l = 0, int r = -1) {
		// デフォルト引数の再設定
		if (r == -1) r = N;

		// 今見てるノードが作用させたい区間から完全に外れてるとき、単位元を返す
		if (r <= L || R <= l) return id_V;

		// 遅延評価
		lazy_evaluation(i, l, r);

		// 今見てるノードが作用させたい区間に完全に収まってるとき、遅延評価済なのでそのノードは正しい値　これを返せばよい
		if (L <= l && r <= R) return V[i];

		// 以下、区間が中途半端に被ってる場合
		// 区間の真ん中
		int mid = (l + r) / 2;
		// 子へ再帰して、返ってきたのを2項演算でマージしてそいつを返す
		return op_V(
			range_fold(L, R, i * 2, l, mid),
			range_fold(L, R, i * 2 + 1, mid, r)
		);
	}

	// おまけ　一点の値が欲しくなったけど双対セグ木を貼るのは面倒臭いとき用
	T operator[](const int& i) {return range_fold(i, i + 1);}
};

//=============================== SegTree ここまで ================================

int main() {
	ios::sync_with_stdio(false);
	cin.tie(nullptr);

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

	vector<modint<P>> A(N, 1);
	for (int i = N - 2; i >= 0; i--) A[i] = A[i + 1] * 10;
	// A = {10^(N-1), 10^(N-2), 10^(N-3), ..., 10^0} を作った

	tenpow = A;
	sum.resize(N + 1, 0);
	for (int i = N - 1; i >= 0; i--) sum[i] = sum[i + 1] + tenpow[i];

	// セグ木に渡すラムダ式群　②と③は a に b を作用させる関数なので a = g(a, b) みたいな使い方をする
	// ①　値配列の2項演算　今回は和
	auto f = [] (modint<P> a, modint<P> b) {return a + b;};
	// ②　遅延配列内の伝播用のラムダ式　今回は代入操作
	auto g = [] (modint<P> a, modint<P> b) {return b;};
	/*	③　遅延配列から値配列へ作用させるためのラムダ式　遅延評価関数内で呼ぶ　多分ここがキモ
	 	a に b を作用　今回は今見てる区間の端の情報も引数で与える必要がある　というか普段からその形式で書く
		区間の情報が必要ない作用を扱うときも引数には毎回与えてやる　要らないなら関数内で使わなければよい
		グローバルに貼ったライブラリには一切手を加えず main 関数から渡すラムダ式ですべて対応できるような抽象化がよい（主観）
		今見てるノードがカバーしてる区間全てを b に書き換えるので、a に入る値は
		b * tenpow[l] + b * tenpow[l+1] + ... + b * tenpow[r-1]
		= b * Σ[l,r-1]tenpow[i]
		= b * (sum[l] - sum[r])
	*/
	auto h = [&] (modint<P> a, modint<P> b, int l, int r) {
		return b * (sum[l] - sum[r]);
	};

	modint<P> id = 0;
	// f は足し算なので単位元は 0
	// g, h は代入操作なので単位元は本来ないが 0 は制約上クエリに現れないので単位元として良さそう
	lazy_segment_tree<modint<P>, modint<P>, decltype(f), decltype(g), decltype(h)> st(A, f, g, h, id, id);

	while (Q--) {

		int L, R;
		modint<P> D;
		cin >> L >> R >> D;
		st.range_update(L - 1, R, D);
		cout << st.range_fold(0, N) << endl;
	}
}

Measured memory_bytes: 16104