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(){
}
const long mod = 998244353;
void solve(){
    int n = scan!int, q = scan!int;
    long[] us = [1];
    long[] power10 = [1];
  
    foreach(i; 0 .. n){
        power10 ~= power10[$ - 1] * 10 % mod;
        us ~= (us[$ - 1] + power10[$ - 1]) % mod;
    }
 
    auto seg = new SegTree!(long, 
            delegate (long v1, long v2) => v2,
            delegate (long x, int wx, long v) => us[wx - 1] * v % mod,
            delegate (long x, int wx, long y, int wy) => (x * power10[wy] + y) % mod,
            0
    )(0, n);
    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;
    }
}


/*
　セグメント木
　区間への書き込み、区間からの読み出し
    使い方
    
    初期化
    auto seg = new SegTree!(long, 
            delegate (long v1, long v2) => v1 + v2,
                // 値 v1 をセットした場所に値 v2 をセットしたら、結局どんな値をセットしたことになるか
                // 加算する場合なら v1 + v2、上書きする場合なら v2 など
            delegate (long x, int wx, long v) => x + v * wx,
                // 計算結果が x である幅 wx の区間に、値 v をセットしたら、計算結果はどうなるか
                // 幅とは、たとえば (i, i + 1) なら幅 1 （(i, i + 1) は 1 マスだけからなる区間）
            delegate (long x, int wx, long y, int wy) => x + y,
                // 計算結果が x である幅 wx の区間と、計算結果が y である幅 wy の区間をつなげたら
                // 合わせた区間の計算結果はどうなるか（x が左、y が右）
            0
                // 初期値 (開始直後に1マスだけ読み取ったら何という計算結果になるか)
                // 一律だと都合が悪い場合は、最初に for(i; 0 .. n ) setValue(i, i + 1, xxxx) で設定すればよい
    )(0, n);
        // 0 以上 n 未満の値を扱う
    
    値の設定
    seg.setValue(l, r + 1, v);
        // l 以上 (r + 1) 未満からなる区間に値 v をセットする
    
    値の取得
    ans = seg.getValue(l, r + 1);
        // l 以上 (r + 1) 未満からなる区間の計算結果
    コピペ用
    auto seg = new SegTree!(long, 
            delegate (long v1, long v2) => v1 + v2,
            delegate (long x, int wx, long v) => x + v * wx,
            delegate (long x, int wx, long y, int wy) => x + y,
            0
    )(0, n);
*/
class SegTree(T, T delegate(T, T) update, T delegate(T, int, T) apply, T delegate(T, int, T, int) merge, T initial){
	int a, b;
	int width;
	bool hasValue;
	SegTree left, right;
	T value, result;
	this(int a, int b){
		this.a = a, this.b = b;
		this.width = b - a;
		if(b - a == 1){
			result = initial;
		}
		else{
			int c = (a + b) / 2;
			left = new SegTree(a, c);
			right = new SegTree(c, b);
			result = merge(left.getResult(), left.width, right.getResult(), right.width);
		}
	}

    // 値の書き込み
	void setValue(T v){ setValue(a, b, v); }
	void setValue(int a, int b, T v){
		if(b <= this.a || this.b <= a) return;
		if(a <= this.a && this.b <= b){
            // 全体への書き込み
			if(hasValue) value = update(value, v);
			else value = v;
			hasValue = 1;
		}
		else{
            // 一部分への書き込み
			divide();
			left.setValue(a, b, v);
			right.setValue(a, b, v);
			result = merge(left.getResult(), left.width, right.getResult(), right.width);
		}
	}

    // 値を下へおろす (内部用)
	void divide(){
		if(hasValue){
			left.setValue(value);
			right.setValue(value);
			hasValue = 0;
			result = merge(left.getResult(), left.width, right.getResult(), right.width);
		}
	}

    // 計算結果の取得
	T getResult(){ return getResult(a, b); }
	T getResult(int a, int b){
		if(b <= this.a || this.b <= a) assert(0);
		if(a <= this.a && this.b <= b){
            // 全体からの取得
			if(hasValue) return apply(result, width, value);
			else return result;
		}
		else{
            // 一部分からの取得
			divide();
			int wleft = left.getWidth(a, b), wright = right.getWidth(a, b);
			if(wleft > 0 && wright > 0) return merge(left.getResult(a, b), wleft, right.getResult(a, b), wright);
			else if(wleft > 0) return left.getResult(a, b);
			else if(wright > 0) return right.getResult(a, b);
            assert(0);
		}
	}

    // 幅の取得 (内部用)
	int getWidth(int a, int b){
		if(b <= this.a || this.b <= a) return 0;
		if(a <= this.a && this.b <= b) return width;
		if(a <= this.a) return b - this.a;
		if(this.b <= b) return this.b - a;
		return b - a;
	}

	// ダンプ (デバッグ用)
	string valueString(){
		string v = (hasValue? value.to!string: "-");
		if(left && right){
			v ~= "\t" ~ left.valueString.split("\n").join("\n|\t") ~ "\n\t" ~ right.valueString.split("\n").join("\n\t");
		}
		return v;
	}
	string resultString(){
		string r = ((result == initial)? "ini": result.to!string);
		if(left && right){
			r ~= "\t" ~ left.resultString.split("\n").join("\n|\t") ~ "\n\t" ~ right.resultString.split("\n").join("\n\t");
		}
		return r;
	}
	override string toString(){
		return "values:\n" ~ valueString ~ "\nresult:\n" ~ resultString;
	}
}

Measured memory_bytes: 71276