#include<iostream>
#include<math.h>
#include<algorithm>
#include<stdint.h>
#include<vector>
#include<deque>
#include<stack>
#include<functional>
#include<string>
#include<cstring>
#include<array>
#include<iomanip>
#include<list>
#include<set>
#include<map>
#include<unordered_map>
#include<unordered_set>
#include<bitset>
#include <queue>
#include<boost/multiprecision/cpp_int.hpp>


using namespace std;
using namespace boost::multiprecision;

using ll = long long;
using ull = unsigned long long;
using uint = unsigned int;
using ldb = long double;

#define REP(i,a,b) for(ll i = a; i < b; ++i)
#define PRI(s) cout << s << endl
#define PRIF(v, n) printf("%."#n"f\n", v)
#define PRIY PRI("Yes")
#define PRIN PRI("No")
template<typename A, typename B>
void mins(A& a, const B& b) { a = min(a, (A)b); };
template<typename A, typename B>
void maxs(A& a, const B& b) { a = max(a, (A)b); };


template<typename T, typename M>
class lazysegtree {
public:
	long long n;
	vector<T> dat;
	vector<M> lazy;
	T et;
	M em;
	function<T(T, T)> op;//(左の子、右の子）
	function<T(M, T)> map;
	function<M(M, M)> com;//(新しい,古い)

	lazysegtree(long long n_, T et, M em, function<T(T, T)> op, function<T(M, T)> map, function<M(M, M)> com)
		:et(et), em(em), op(op), map(map), com(com) {
		n = (1 << (int)ceil(log2(n_)));
		dat.resize(2 * n-1, et);
		lazy.resize(2 * n-1, em);
	}

	void prop(int ind) {
		dat[ind] = map(lazy[ind], dat[ind]);
		if (ind < n - 1) {
			lazy[2 * ind + 1] = com(lazy[ind], lazy[2 * ind + 1]);
			lazy[2 * ind + 2] = com(lazy[ind], lazy[2 * ind + 2]);
		}
		lazy[ind] = em;
	}

	T query(int a, int b, int l = 0, int r = -1, int ind = 0) {
		if (r == -1) r = n;
		if (r <= a || b <= l) return et;

		prop(ind);

		if (a <= l && r <= b)return dat[ind];
		else return op(
			query(a, b, l, (l + r) / 2, 2 * ind + 1),
			query(a, b, (l + r) / 2, r, 2 * ind + 2)
			);
	}

	T apply(M m, int a, int b, int l = 0, int r = -1, int ind = 0) {
		if (r == -1) r = n;
		if (r <= a || b <= l) {
			prop(ind);
		}
		else if (a <= l && r <= b) {
			lazy[ind] = com(m, lazy[ind]);
			prop(ind);
		}
		else {
			prop(ind);
			dat[ind] = op(
				apply(m, a, b, l, (l + r) / 2, 2 * ind + 1),
				apply(m, a, b, (l + r) / 2, r, 2 * ind + 2)
				);
		}
		return dat[ind];
	}

	void build() {
		REP(i, 0, 2 * n - 1) prop(i);
		for (int i = n - 2; i >= 0; --i) dat[i] = op(dat[2 * i + 1], dat[2 * i + 2]);
	}
};

ll mod = 998244353;

//拡張ユークリッドの互除法。戻り値は最大公約数、x,yはax+by=gcd(a,b)を満たす組の一つ
long long gcdext(long long a, long long b, long long& x, long long& y) {
	if (b == 0) {
		x = 1;
		y = 0;
		return a;
	}
	long long g = gcdext(b, a % b, y, x);
	y -= a / b * x;
	return g;
}

//モジュラ逆数
long long modinv(long long a, long long mod) {
	long long x, y;
	gcdext(a, mod, x, y);
	x = ((x % mod) + mod) % mod;
	return x;
}
ll modpow(ll r, ll n, ll mod) {
	ll ans = 1;
	ll tmp = r;
	while (n > 0) {
		if ((n & 1) > 0) {
			ans = ans * tmp % mod;
		}
		tmp = tmp * tmp % mod;
		n >>= 1;
	}
	return ans;
}

struct item {
	ll x, i, k;
};


ll em = -1;
item et = { 0,-1,0 };

vector<ll> tbl10;
ll inv9;

ll com(ll a, ll b) { 
	if (a == -1)return b;
	if (b == -1) return a;
	return a; 
}
item mp(ll s, item a) {
	if (s == -1)return a;

	ll q = (tbl10[a.k] - 1);
	q = q* inv9% mod;
	q = q*s% mod;
	
	return { q, a.i, a.k };
}
item op(item a, item b) {
	if (a.i < b.i) swap(a, b);
	if (b.i == -1) return a;
	return { (a.x *tbl10[a.i - b.i] % mod + b.x) % mod, b.i, a.k + b.k };
}

int main() {
	ll N, Q; cin >> N >> Q;
	lazysegtree<item, ll> tree{ N,et,em, op,mp,com };
	inv9 = modinv(9, mod);
	tbl10.resize(N + 2);
	tbl10[0] = 1;
	REP(i, 1, N + 2)tbl10[i] = tbl10[i - 1] * 10 % mod;
	REP(i, 0, N) {
		tree.dat[tree.n - 1 + i] = item{ 1, N - 1 - i, 1 };
	}
	tree.build();
	REP(i, 0, Q) {
		ll l, r, d;
		cin >> l >> r >> d;
		tree.apply(d, l - 1, r);
		PRI(tree.query(0, N).x);
	}
	return 0;
}

Measured memory_bytes: 21284