import std.stdio, std.conv, std.string, std.array, std.range, std.algorithm, std.container;
import std.math, std.random, std.bigint, std.datetime, std.format;
bool DEBUG = 0; void log(A ...)(lazy A a){ if(DEBUG) print(a); }
void main(string[] args){ args ~= ["", ""]; string cmd = args[1]; if(cmd == "-debug") DEBUG = 1;
if(cmd == "-gen") gen; else if(cmd == "-jury") jury; else solve; } 
void print(){ writeln(""); } void print(T)(T t){ writeln(t); } void print(T, A ...)(T t, A a){ std.stdio.write(t, " "), print(a); }
string unsplit(T)(T xs){ return xs.array.to!(string[]).join(" "); }
string picture(bool[] xs){ return xs.array.map!(x => x? '#': '.').array; } string picture(bool[][] xf){ return xf.map!(xs => xs.picture).array.join("\n"); }
string scan(){ static string[] ss; while(!ss.length) ss = readln.chomp.split; string res = ss[0]; ss.popFront; return res; }
T scan(T)(){ return scan.to!T; } T[] scan(T)(int n){ return n.iota.map!(i => scan!T()).array; }
T lowerTo(T)(ref T x, T y){ if(x > y) x = y; return x; } T raiseTo(T)(ref T x, T y){ if(x < y) x = y; return x; }

// ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- //
void gen(){
}
void jury(){
}
struct P{ long value; int count; }
const long mod = 998244353;
long[] us = [1];
long[] power10 = [1];
void solve(){
    int n = scan!int, q = scan!int;

    foreach(i; 0 .. n){
        power10 ~= power10[$ - 1] * 10 % mod;
        us ~= (us[$ - 1] + power10[$ - 1]) % mod;
    }
    log("power10:", power10);
    log("us:", us);

    auto seg = new SegTree!long(0, n,
        (long x, long y) {log("apply", x, y); return y;},
        (long x, int wx, long y) {log("applyToResult", x, wx, y); return us[wx - 1] * y % mod;},
        (long x, int wx, long y, int wy) {log("merge", x, wx, y, wy); return (x * power10[wy] + y) % mod; },
        0
    );
    foreach(i; 0 .. n) seg.setValue(i, i + 1, 1);
    log("seg:", seg);

    foreach(_; 0 .. q){
        int l = scan!int - 1, r = scan!int - 1;
        long d = scan!long;
        seg.setValue(l, r + 1, d);
        log("seg:", seg);
        seg.getResult(0, n).print;
    }

}


 
 /*
セグメント木 区分木 segtree segment tree
範囲に書き込み、範囲で読み出す。
たとえば、 [l, r) の範囲を x に更新するクエリがきて
[l, r) の範囲の和を求めるなど。
※書き込み操作は、べき等でなければならない
（f(x, y) = f( f(x, y), y)）
たとえば、典型的には 上書き代入（つまり f(x, y) = y）
あとは &= とか…？（上書き代入以外は未テスト）
使用例
auto seg = new SegTree!long(0, n, (x, wx, y) => y * wx, (x, wx, y, wy) => x + y, 0);
引数
a, b　添字の範囲。 この意味はa から b - 1 までなので注意。
apply　すでに値 x があるところに y を与えるとどうなるか ※べき等な演算である必要がある
merge　左の子と右の子から自分の値をきめる関数（たとえばmax）
neutral　すべての x に対して merge(x, z) = merge(z, x) = x をみたすz（たとえば-infty）
*/
class SegTree(T){
    T delegate(T, T) apply;
	T delegate(T, int, T) applyToResult;
	T delegate(T, int, T, int) merge;
	T neutral;
	int a, b; // a <= x < b を担当する
	bool hasValue, hasResult;
	SegTree left, right; // 左の子(a <= x < c)、右の子(c <= x < b)
	T value, result;
	this(int a, int b, T delegate(T, T) apply,  T delegate(T, int, T) applyToResult,
            T delegate(T, int, T, int) merge, T neutral){
		this.a = a, this.b = b;
		this.apply = apply;
		this.applyToResult = applyToResult;
        this.merge = merge;
		this.neutral = neutral;
		if(b - a == 1){
    		this.value = neutral;
            this.result = neutral;
		}
		else{
			int c = (a + b) / 2;
			left = new SegTree(a, c, apply, applyToResult, merge, neutral);
			right = new SegTree(c, b, apply, applyToResult, merge, neutral);
            this.result = merge(
                left.getResult(left.a, left.b), left.b - left.a,
                right.getResult(right.a, right.b), right.b - right.a
            );
		}
		this.hasValue = 1;
        this.hasResult = 1;
	}
	
	// [a, b) に値を設定する ※[a, b) はこのノードの担当区間とかぶっていなくてもよい
	void setValue(int a, int b, T value){
        log("setValue", "(", this.a, ",", this.b, ") (", a, ",", b, ",", value, ")");
		if(b <= this.a || this.b <= a) return;
		if(a <= this.a && this.b <= b){
			this.hasValue = true;
			this.value = apply(this.value, value);
		}
		else{
			if(this.hasValue){
				left.setValue(left.a, left.b, this.value);
				right.setValue(right.a, right.b, this.value);
				this.hasValue = false;
			}
			left.setValue(a, b, value);
			right.setValue(a, b, value);
            this.hasResult = false;
		}
        log("setValue", "(", this.a, ",", this.b, ") Done.");
	}
	
	// [a, b) における値を読む ※[a, b) はこのノードの担当区間とかぶっていなくてもよい
	T getResult(int a, int b){
        log("getResult", "(", this.a, ",", this.b, ") (", a, ",", b, ")");
		if(b <= this.a || this.b <= a) return neutral;
		if(a <= this.a && this.b <= b){
			if(! this.hasResult){
				T v1 = this.left.getResult(a, b), v2 = this.right.getResult(a, b);
                int w1 = left.b - left.a, w2 = right.b - right.a;
	            log("getResult", "(", this.a, ",", this.b, ") v1:", v1, "w1:", w1, "v2:", v2, "w2:", w2);
				T v = merge(v1, w1, v2, w2);
				this.result = v;
				this.hasResult = 1;
			}
            log("getResult", "(", this.a, ",", this.b, ") ", this.result, "- this.result");
            if(this.hasValue) return applyToResult(this.result, this.b - this.a, this.value);
            else return this.result;
		}
		else{
            if(this.hasValue){
				left.setValue(left.a, left.b, this.value);
				right.setValue(right.a, right.b, this.value);
				this.hasValue = false;
            }
			T v1 = this.left.getResult(a, b), v2 = this.right.getResult(a, b);
            int w1 = min(b, left.b) - max(a, left.a), w2 = min(b, right.b) - max(a, right.a);
            log("getResult", "(", this.a, ",", this.b, ") v1:", v1, "w1:", w1, "v2:", v2, "w2:", w2);
			T v = merge(v1, w1, v2, w2);
            log("getResult", "(", this.a, ",", this.b, ") ", v, "- merged");
			return v;
		}
	}
	
	// 配列に変換したもの（デバッグ用）
	T[] array(){
		T[] res;
		foreach(i; a .. b) res ~= this.value;
		return res;
	}
	
	// 文字列に変換したもの（デバッグ用）
	override string toString(){
		if(this.hasValue) return this.array.to!string;
		else return left.toString ~ right.toString;
	}
}
