Buggy python submission (verdict: Time Limit Exceeded):
MOD = 998244353

#####ide_ele#####
ide_ele = [0, 0]
#################

#####segfunc#####
def segfunc(x, y):
    xa, xb = x
    ya, yb = y
    return [(xa + ya) % MOD, (xb + yb) % MOD]
#################

#####triv_act#####
triv_act = 0
#################

#####action#####
def action(x, p):
    if p != triv_act:
      _, xb = x
      return [(xb * p) % MOD , xb]
    return x
#################

class LazySegmentTree:
    """
    init(init_val, ide_ele): 配列init_valで初期化 O(N)
    update(l, r, x): 区間[l, r)をxに更新 O(logN)
    query(l, r): 区間[l, r)をsegfuncしたものを返す O(logN)
    """
    def __init__(self, init_val, segfunc, ide_ele, action, triv_act):
        """
        init_val: 配列の初期値
        segfunc: 区間にしたい操作
        action: モノイドへの作用
        ide_ele: 単位元
        triv_act: モノイドへ自明に作用する元
        num: n以上の最小の2のべき乗
        data: 値配列(1-index)
        lazy: 遅延配列(1-index)
        """
        n = len(init_val)
        self.segfunc = segfunc
        self.action = action
        self.ide_ele = ide_ele
        self.triv_act = triv_act
        self.num = 1 << (n - 1).bit_length()
        self.data = [ide_ele] * 2 * self.num
        self.lazy = [triv_act] * 2 * self.num
        # 配列の値を葉にセット
        for i in range(n):
            self.data[self.num + i] = init_val[i]
        # 構築していく
        for i in range(self.num - 1, 0, -1):
            self.data[i] = self.segfunc(self.data[2 * i], self.data[2 * i + 1])

    def gindex(self, l, r):
            """
            伝搬する対象の区間を求める
            lm: 伝搬する必要のある最大の左閉区間
            rm: 伝搬する必要のある最大の右開区間
            """
            l += self.num
            r += self.num
            lm = (l // (l & -l)) >> 1
            rm = (r // (r & -r)) >> 1
            ids = []

            while r > l > 0:
                if l <= lm:
                    ids.append(l)
                if r <= rm:
                    ids.append(r)
                r >>= 1
                l >>= 1
            while l > 0:
                ids.append(l)
                l >>= 1
            return ids                

    def propagates(self, ids):
        """
        遅延伝搬処理
        ids: 伝搬する対象の区間 
        """
        for j in range(len(ids)-1, -1, -1):
            i = ids[j]
            v = self.lazy[i]
            if v == triv_act:
                continue
            lg = self.data[2 * i][1].bit_length()-1
            self.lazy[2 * i] = v
            self.lazy[2 * i + 1] = v
            self.data[2 * i] = action(self.data[2 * i], v)
            self.data[2 * i + 1] = action(self.data[2 * i + 1], v)
            self.lazy[i] = self.triv_act

    def update(self, l, r, x):
        """
        区間[l, r)の値をxに更新
        l, r: index(0-index)
        x: update value
        """
        ids = self.gindex(l, r)
        self.propagates(ids)
        l += self.num
        r += self.num
        while l < r:
            if l & 1:
                self.lazy[l] = x
                self.data[l] = action(self.data[l], x)
                l += 1
            if r & 1:
                self.lazy[r - 1] = x
                self.data[r - 1] = action(self.data[r - 1], x)
            r >>= 1
            l >>= 1
        for i in ids:
            self.data[i] = self.segfunc(self.data[2 * i], self.data[2 * i + 1])


    def query(self, l, r):
        """
        [l, r)のsegfuncしたものを得る
        l: index(0-index)
        r: index(0-index)
        """
        #*ids, = self.gindex(l, r)
        self.propagates(self.gindex(l, r))

        res_l = self.ide_ele
        res_r = self.ide_ele

        l += self.num
        r += self.num
        while l < r:
            if l & 1:
                res_l = self.segfunc(res_l, self.data[l])
                l += 1
            if r & 1:
                res_r = self.segfunc(self.data[r - 1], res_r)
            l >>= 1
            r >>= 1
        return self.segfunc(res_l, res_r)      
      
import sys
input = sys.stdin.buffer.readline

N, Q = map(int,input().split()) #3*10**5+5, 10**5 
init = [[1,1] for _ in range(N)]

tmp = 1
for i in range(N-2,-1,-1):
  tmp = (tmp * 10) % MOD
  init[i][0], init[i][1] = tmp, tmp
lst = LazySegmentTree(init, segfunc, ide_ele, action, triv_act)
#print(lst.data[1], lst.data)

ans = [0 for _ in range(Q)]
for i in range(Q):
  L, R, D = map(int,input().split()) #i, N//2+1, i*4 % 9 + 1
  L -= 1
  R -= 1
  lst.update(L, R+1, D)
  #print(lst.data[1], lst.data)
  ans[i] = lst.data[1][0]
print(*ans, sep="\n")
#print(lst.data)

Fixed submission:
class LazySegmentTree():
  def __init__(self, n, op, e, mapping, composition, id):
    self.n = n
    self.op = op
    self.e = e
    self.mapping = mapping
    self.composition = composition
    self.id = id
    self.log = (n - 1).bit_length()
    self.size = 1 << self.log
    self.data = [(e,e)] * (2 * self.size)
    self.lazy = [id] * (self.size)

  def update(self, k):
    self.data[k] = self.op(self.data[2 * k], self.data[2 * k + 1])

  def all_apply(self, k, f):
    self.data[k] = self.mapping(f, self.data[k])
    if k < self.size:
      self.lazy[k] = self.composition(f, self.lazy[k])

  def push(self, k):
    self.all_apply(2 * k, self.lazy[k])
    self.all_apply(2 * k + 1, self.lazy[k])
    self.lazy[k] = self.id

  def build(self, arr):
    #assert len(arr) == self.n
    for i, a in enumerate(arr):
      self.data[self.size + i] = a
    for i in range(self.size-1,0,-1):
      self.update(i)

  def set(self, p, x):
    #assert 0 <= p < self.n
    p += self.size
    for i in range(self.log,0,-1):
      self.push(p >> i)
    self.data[p] = x
    for i in range(1, self.log + 1):
      self.update(p >> i)

  def get(self, p):
    #assert 0 <= p < self.n
    p += self.size
    for i in range(1, self.log + 1):
      self.push(p >> i)
    return self.data[p]

  def prod(self, l, r):
    #assert 0 <= l <= r <= self.n
    if l == r: return self.e
    l += self.size
    r += self.size
    for i in range(1, self.log + 1)[::-1]:
      if ((l >> i) << i) != l: self.push(l >> i)
      if ((r >> i) << i) != r: self.push(r >> i)
    sml = smr = self.e
    while l < r:
      if l & 1:
        sml = self.op(sml, self.data[l])
        l += 1
      if r & 1:
        r -= 1
        smr = self.op(self.data[r], smr)
      l >>= 1
      r >>= 1
    return self.op(sml, smr)

  def apply(self, p, f):
    #assert 0 <= p < self.n
    p += self.size
    for i in range(1, self.log + 1)[::-1]:
      self.push(p >> i)
    self.data[p] = self.mapping(f, self.data[p])
    for i in range(1, self.log + 1):
      self.update(p >> i)

  def range_apply(self, l, r, f):
    #assert 0 <= l <= r <= self.n
    if l == r: return
    l += self.size
    r += self.size
    for i in range(1, self.log + 1)[::-1]:
      if ((l >> i) << i) != l: self.push(l >> i)
      if ((r >> i) << i) != r: self.push((r - 1) >> i)
    l2 = l
    r2 = r
    while l < r:
      if l & 1:
        self.all_apply(l, f)
        l += 1
      if r & 1:
        r -= 1
        self.all_apply(r, f)
      l >>= 1
      r >>= 1
    l = l2
    r = r2
    for i in range(1, self.log + 1):
      if ((l >> i) << i) != l: self.update(l >> i)
      if ((r >> i) << i) != r: self.update((r - 1) >> i)

  def max_right(self, l, g):
    #assert 0 <= l <= self.n
    #assert g(self.e)
    if l == self.n: return self.n
    l += self.size
    for i in range(1, self.log + 1)[::-1]:
      self.push(l >> i)
    sm = self.e
    while True:
      while l % 2 == 0: l >>= 1
      if not g(self.op(sm, self.data[l])):
        while l < self.size:
          self.push(l)
          l = 2 * l
          if g(self.op(sm, self.data[l])):
            sm = self.op(sm, self.data[l])
            l += 1
        return l - self.size
      sm = self.op(sm, self.data[l])
      l += 1
      if (l & -l) == l: return self.n

  def min_left(self, r, g):
    #assert 0 <= r <= self.n
    #assert g(self.e)
    if r == 0: return 0
    r += self.size
    for i in range(1, self.log + 1)[::-1]:
      self.push((r - 1) >> i)
    sm = self.e
    while True:
      r -= 1
      while r > 1 and r % 2: r >>= 1
      if not g(self.op(self.data[r], sm)):
        while r < self.size:
          self.push(r)
          r = 2 * r + 1
          if g(self.op(self.data[r], sm)):
            sm = self.op(self.data[r], sm)
            r -= 1
        return r + 1 - self.size
      sm = self.op(self.data[r], sm)
      if (r & -r) == r: return 0

import sys
input = sys.stdin.buffer.readline

INF = 10**18
MOD = 998244353

N, Q = map(int, input().split())

def op(x, y):
  xv, xr = x
  yv, yr = y
  return (xv + yv) % MOD, (xr + yr) % MOD

def mapping(p, x): #pが更新後の値, xが更新する前の値
  xv, xr = x
  if p != INF:
    return p * xr % MOD, xr
  return x

def composition(p, q):
  if p != INF:
    return p
  return q

lis = [[1,1] for _ in range(N)]
tmp = 1
for i in range(N-2,-1,-1):
  tmp *= 10 
  tmp %= MOD
  lis[i][0], lis[i][1] = tmp, tmp
#print(lis)
#arr = [(e,e) for e in lis]

e = 0
id = INF

lst = LazySegmentTree(N, op, e, mapping, composition, id)
lst.build(lis)

ans = [0]*Q

for i in range(Q):
  l, r, d = map(int, input().split())
  lst.range_apply(l - 1, r, d)
  ans[i] = lst.data[1][0]


print(*ans, sep='\n')

Buggy python submission (verdict: Time Limit Exceeded):
MOD = 998244353

#####ide_ele#####
ide_ele = [0, 0]
#################

#####segfunc#####
def segfunc(x, y):
    xa, xb = x
    ya, yb = y
    return [(xa + ya) % MOD, (xb + yb) % MOD]
#################

#####triv_act#####
triv_act = 0
#################

#####action#####
def action(x, p):
    if p != triv_act:
      _, xb = x
      return [(xb * p) % MOD , xb]
    return x
#################

class LazySegmentTree:
    """
    init(init_val, ide_ele): 配列init_valで初期化 O(N)
    update(l, r, x): 区間[l, r)をxに更新 O(logN)
    query(l, r): 区間[l, r)をsegfuncしたものを返す O(logN)
    """
    def __init__(self, init_val, segfunc, ide_ele, action, triv_act):
        """
        init_val: 配列の初期値
        segfunc: 区間にしたい操作
        action: モノイドへの作用
        ide_ele: 単位元
        triv_act: モノイドへ自明に作用する元
        num: n以上の最小の2のべき乗
        data: 値配列(1-index)
        lazy: 遅延配列(1-index)
        """
        n = len(init_val)
        self.segfunc = segfunc
        self.action = action
        self.ide_ele = ide_ele
        self.triv_act = triv_act
        self.num = 1 << (n - 1).bit_length()
        self.data = [ide_ele] * 2 * self.num
        self.lazy = [triv_act] * 2 * self.num
        # 配列の値を葉にセット
        for i in range(n):
            self.data[self.num + i] = init_val[i]
        # 構築していく
        for i in range(self.num - 1, 0, -1):
            self.data[i] = self.segfunc(self.data[2 * i], self.data[2 * i + 1])

    def gindex(self, l, r):
            """
            伝搬する対象の区間を求める
            lm: 伝搬する必要のある最大の左閉区間
            rm: 伝搬する必要のある最大の右開区間
            """
            l += self.num
            r += self.num
            lm = (l // (l & -l)) >> 1
            rm = (r // (r & -r)) >> 1

            while r > l > 0:
                if l <= lm:
                    yield l
                if r <= rm:
                    yield r
                r >>= 1
                l >>= 1
            while l > 0:
                yield l
                l >>= 1

    def propagates(self, *ids):
        """
        遅延伝搬処理
        ids: 伝搬する対象の区間 
        """
        for j in range(len(ids)-1, -1, -1):
            i = ids[j]
            v = self.lazy[i]
            if v == triv_act:
                continue
            lg = self.data[2 * i][1].bit_length()-1
            self.lazy[2 * i] = v
            self.lazy[2 * i + 1] = v
            self.data[2 * i] = action(self.data[2 * i], v)
            self.data[2 * i + 1] = action(self.data[2 * i + 1], v)
            self.lazy[i] = self.triv_act

    def update(self, l, r, x):
        """
        区間[l, r)の値をxに更新
        l, r: index(0-index)
        x: update value
        """
        *ids, = self.gindex(l, r)
        self.propagates(*ids)
        l += self.num
        r += self.num
        while l < r:
            if l & 1:
                self.lazy[l] = x
                self.data[l] = action(self.data[l], x)
                l += 1
            if r & 1:
                self.lazy[r - 1] = x
                self.data[r - 1] = action(self.data[r - 1], x)
            r >>= 1
            l >>= 1
        for i in ids:
            self.data[i] = self.segfunc(self.data[2 * i], self.data[2 * i + 1])


    def query(self, l, r):
        """
        [l, r)のsegfuncしたものを得る
        l: index(0-index)
        r: index(0-index)
        """
        *ids, = self.gindex(l, r)
        self.propagates(*ids)

        res_l = self.ide_ele
        res_r = self.ide_ele

        l += self.num
        r += self.num
        while l < r:
            if l & 1:
                res_l = self.segfunc(res_l, self.data[l])
                l += 1
            if r & 1:
                res_r = self.segfunc(self.data[r - 1], res_r)
            l >>= 1
            r >>= 1
        return self.segfunc(res_l, res_r)      
      
import sys
input = sys.stdin.buffer.readline

N, Q = map(int,input().split()) #3*10**5+5, 10**5 
init = [[1,1] for _ in range(N)]

tmp = 1
for i in range(N-2,-1,-1):
  tmp = (tmp * 10) % MOD
  init[i][0], init[i][1] = tmp, tmp
lst = LazySegmentTree(init, segfunc, ide_ele, action, triv_act)
#print(lst.data[1], lst.data)

ans = [0 for _ in range(Q)]
for i in range(Q):
  L, R, D = map(int,input().split()) #i, N//2+1, i*4 % 9 + 1
  L -= 1
  R -= 1
  lst.update(L, R+1, D)
  #print(lst.data[1], lst.data)
  ans[i] = lst.data[1][0]
print(*ans, sep="\n")
#print(lst.data)

Fixed submission:
class LazySegmentTree():
  def __init__(self, n, op, e, mapping, composition, id):
    self.n = n
    self.op = op
    self.e = e
    self.mapping = mapping
    self.composition = composition
    self.id = id
    self.log = (n - 1).bit_length()
    self.size = 1 << self.log
    self.data = [(e,e)] * (2 * self.size)
    self.lazy = [id] * (self.size)

  def update(self, k):
    self.data[k] = self.op(self.data[2 * k], self.data[2 * k + 1])

  def all_apply(self, k, f):
    self.data[k] = self.mapping(f, self.data[k])
    if k < self.size:
      self.lazy[k] = self.composition(f, self.lazy[k])

  def push(self, k):
    self.all_apply(2 * k, self.lazy[k])
    self.all_apply(2 * k + 1, self.lazy[k])
    self.lazy[k] = self.id

  def build(self, arr):
    #assert len(arr) == self.n
    for i, a in enumerate(arr):
      self.data[self.size + i] = a
    for i in range(self.size-1,0,-1):
      self.update(i)

  def set(self, p, x):
    #assert 0 <= p < self.n
    p += self.size
    for i in range(self.log,0,-1):
      self.push(p >> i)
    self.data[p] = x
    for i in range(1, self.log + 1):
      self.update(p >> i)

  def get(self, p):
    #assert 0 <= p < self.n
    p += self.size
    for i in range(1, self.log + 1):
      self.push(p >> i)
    return self.data[p]

  def prod(self, l, r):
    #assert 0 <= l <= r <= self.n
    if l == r: return self.e
    l += self.size
    r += self.size
    for i in range(1, self.log + 1)[::-1]:
      if ((l >> i) << i) != l: self.push(l >> i)
      if ((r >> i) << i) != r: self.push(r >> i)
    sml = smr = self.e
    while l < r:
      if l & 1:
        sml = self.op(sml, self.data[l])
        l += 1
      if r & 1:
        r -= 1
        smr = self.op(self.data[r], smr)
      l >>= 1
      r >>= 1
    return self.op(sml, smr)

  def apply(self, p, f):
    #assert 0 <= p < self.n
    p += self.size
    for i in range(1, self.log + 1)[::-1]:
      self.push(p >> i)
    self.data[p] = self.mapping(f, self.data[p])
    for i in range(1, self.log + 1):
      self.update(p >> i)

  def range_apply(self, l, r, f):
    #assert 0 <= l <= r <= self.n
    if l == r: return
    l += self.size
    r += self.size
    for i in range(1, self.log + 1)[::-1]:
      if ((l >> i) << i) != l: self.push(l >> i)
      if ((r >> i) << i) != r: self.push((r - 1) >> i)
    l2 = l
    r2 = r
    while l < r:
      if l & 1:
        self.all_apply(l, f)
        l += 1
      if r & 1:
        r -= 1
        self.all_apply(r, f)
      l >>= 1
      r >>= 1
    l = l2
    r = r2
    for i in range(1, self.log + 1):
      if ((l >> i) << i) != l: self.update(l >> i)
      if ((r >> i) << i) != r: self.update((r - 1) >> i)

  def max_right(self, l, g):
    #assert 0 <= l <= self.n
    #assert g(self.e)
    if l == self.n: return self.n
    l += self.size
    for i in range(1, self.log + 1)[::-1]:
      self.push(l >> i)
    sm = self.e
    while True:
      while l % 2 == 0: l >>= 1
      if not g(self.op(sm, self.data[l])):
        while l < self.size:
          self.push(l)
          l = 2 * l
          if g(self.op(sm, self.data[l])):
            sm = self.op(sm, self.data[l])
            l += 1
        return l - self.size
      sm = self.op(sm, self.data[l])
      l += 1
      if (l & -l) == l: return self.n

  def min_left(self, r, g):
    #assert 0 <= r <= self.n
    #assert g(self.e)
    if r == 0: return 0
    r += self.size
    for i in range(1, self.log + 1)[::-1]:
      self.push((r - 1) >> i)
    sm = self.e
    while True:
      r -= 1
      while r > 1 and r % 2: r >>= 1
      if not g(self.op(self.data[r], sm)):
        while r < self.size:
          self.push(r)
          r = 2 * r + 1
          if g(self.op(self.data[r], sm)):
            sm = self.op(self.data[r], sm)
            r -= 1
        return r + 1 - self.size
      sm = self.op(self.data[r], sm)
      if (r & -r) == r: return 0

import sys
input = sys.stdin.buffer.readline

INF = 10**18
MOD = 998244353

N, Q = map(int, input().split())

def op(x, y):
  xv, xr = x
  yv, yr = y
  return (xv + yv) % MOD, (xr + yr) % MOD

def mapping(p, x): #pが更新後の値, xが更新する前の値
  xv, xr = x
  if p != INF:
    return p * xr % MOD, xr
  return x

def composition(p, q):
  if p != INF:
    return p
  return q

lis = [[1,1] for _ in range(N)]
tmp = 1
for i in range(N-2,-1,-1):
  tmp *= 10 
  tmp %= MOD
  lis[i][0], lis[i][1] = tmp, tmp
#print(lis)
#arr = [(e,e) for e in lis]

e = 0
id = INF

lst = LazySegmentTree(N, op, e, mapping, composition, id)
lst.build(lis)

ans = [0]*Q

for i in range(Q):
  l, r, d = map(int, input().split())
  lst.range_apply(l - 1, r, d)
  ans[i] = lst.data[1][0]


print(*ans, sep='\n')

Buggy python submission (verdict: Time Limit Exceeded):
MOD = 998244353

#Ref = [0 for _ in range(100)]
#pow_2_lv = [pow(2,lv) for lv in range(100)]
#P = 3*10**5+1
#pow_10_MOD = [1 for i in range(P)]
#for i in range(1,P):
#  pow_10_MOD[i] = (pow_10_MOD[i-1] * 10) % MOD

#Ref[0] = 1
#for s in range(1,P.bit_length()):
#    Ref[s] += (Ref[s-1] * (pow_10_MOD[pow_2_lv[s-1]] + 1)) % MOD
#    Ref[s] %= MOD

#####ide_ele#####
ide_ele = [0, 0]
#################

#####segfunc#####
def segfunc(x, y):
    xa, xb = x
    ya, yb = y
    return [(xa + ya) % MOD, (xb + yb) % MOD]
#################

#####triv_act#####
triv_act = 0
#################

#####action#####
def action(x, p):
    if p != triv_act:
      _, xb = x
      return [(xb * p) % MOD , xb]
    return x
#################

class LazySegmentTree:
    """
    init(init_val, ide_ele): 配列init_valで初期化 O(N)
    update(l, r, x): 区間[l, r)をxに更新 O(logN)
    query(l, r): 区間[l, r)をsegfuncしたものを返す O(logN)
    """
    def __init__(self, init_val, segfunc, ide_ele, action, triv_act):
        """
        init_val: 配列の初期値
        segfunc: 区間にしたい操作
        action: モノイドへの作用
        ide_ele: 単位元
        triv_act: モノイドへ自明に作用する元
        num: n以上の最小の2のべき乗
        data: 値配列(1-index)
        lazy: 遅延配列(1-index)
        """
        n = len(init_val)
        self.segfunc = segfunc
        self.action = action
        self.ide_ele = ide_ele
        self.triv_act = triv_act
        self.num = 1 << (n - 1).bit_length()
        self.data = [ide_ele] * 2 * self.num
        self.lazy = [triv_act] * 2 * self.num
        # 配列の値を葉にセット
        for i in range(n):
            self.data[self.num + i] = init_val[i]
        # 構築していく
        for i in range(self.num - 1, 0, -1):
            self.data[i] = self.segfunc(self.data[2 * i], self.data[2 * i + 1])

    def gindex(self, l, r):
            """
            伝搬する対象の区間を求める
            lm: 伝搬する必要のある最大の左閉区間
            rm: 伝搬する必要のある最大の右開区間
            """
            l += self.num
            r += self.num
            lm = (l // (l & -l)) >> 1
            rm = (r // (r & -r)) >> 1

            while r > l > 0:
                if l <= lm:
                    yield l
                if r <= rm:
                    yield r
                r >>= 1
                l >>= 1
            while l > 0:
                yield l
                l >>= 1

    def propagates(self, *ids):
        """
        遅延伝搬処理
        ids: 伝搬する対象の区間 
        """
        for i in reversed(ids):
            v = self.lazy[i]
            if v == triv_act:
                continue
            lg = self.data[2 * i][1].bit_length()-1
            self.lazy[2 * i] = v
            self.lazy[2 * i + 1] = v
            self.data[2 * i] = action(self.data[2 * i], v)
            self.data[2 * i + 1] = action(self.data[2 * i + 1], v)
            self.lazy[i] = self.triv_act

    def update(self, l, r, x):
        """
        区間[l, r)の値をxに更新
        l, r: index(0-index)
        x: update value
        """
        *ids, = self.gindex(l, r)
        self.propagates(*ids)
        l += self.num
        r += self.num
        while l < r:
            if l & 1:
                self.lazy[l] = x
                self.data[l] = action(self.data[l], x)
                l += 1
            if r & 1:
                self.lazy[r - 1] = x
                self.data[r - 1] = action(self.data[r - 1], x)
            r >>= 1
            l >>= 1
        for i in ids:
            self.data[i] = self.segfunc(self.data[2 * i], self.data[2 * i + 1])


    def query(self, l, r):
        """
        [l, r)のsegfuncしたものを得る
        l: index(0-index)
        r: index(0-index)
        """
        *ids, = self.gindex(l, r)
        self.propagates(*ids)

        res_l = self.ide_ele
        res_r = self.ide_ele

        l += self.num
        r += self.num
        while l < r:
            if l & 1:
                res_l = self.segfunc(res_l, self.data[l])
                l += 1
            if r & 1:
                res_r = self.segfunc(self.data[r - 1], res_r)
            l >>= 1
            r >>= 1
        return self.segfunc(res_l, res_r)      
      
import sys
input = sys.stdin.buffer.readline

N, Q = map(int,input().split()) #3*10**5+5, 10**5 
init = [[1,1] for _ in range(N)]

tmp = 1
for i in range(N-2,-1,-1):
  tmp = (tmp * 10) % MOD
  init[i][0], init[i][1] = tmp, tmp
lst = LazySegmentTree(init, segfunc, ide_ele, action, triv_act)
#print(lst.data[1], lst.data)

ans = [0 for _ in range(Q)]
for i in range(Q):
  L, R, D = map(int,input().split()) #i, N//2+1, i*4 % 9 + 1
  L -= 1
  R -= 1
  lst.update(L, R+1, D)
  #print(lst.data[1], lst.data)
  ans[i] = lst.data[1][0]
print(*ans, sep="\n")
#print(lst.data)

Fixed submission:
class LazySegmentTree():
  def __init__(self, n, op, e, mapping, composition, id):
    self.n = n
    self.op = op
    self.e = e
    self.mapping = mapping
    self.composition = composition
    self.id = id
    self.log = (n - 1).bit_length()
    self.size = 1 << self.log
    self.data = [(e,e)] * (2 * self.size)
    self.lazy = [id] * (self.size)

  def update(self, k):
    self.data[k] = self.op(self.data[2 * k], self.data[2 * k + 1])

  def all_apply(self, k, f):
    self.data[k] = self.mapping(f, self.data[k])
    if k < self.size:
      self.lazy[k] = self.composition(f, self.lazy[k])

  def push(self, k):
    self.all_apply(2 * k, self.lazy[k])
    self.all_apply(2 * k + 1, self.lazy[k])
    self.lazy[k] = self.id

  def build(self, arr):
    #assert len(arr) == self.n
    for i, a in enumerate(arr):
      self.data[self.size + i] = a
    for i in range(self.size-1,0,-1):
      self.update(i)

  def set(self, p, x):
    #assert 0 <= p < self.n
    p += self.size
    for i in range(self.log,0,-1):
      self.push(p >> i)
    self.data[p] = x
    for i in range(1, self.log + 1):
      self.update(p >> i)

  def get(self, p):
    #assert 0 <= p < self.n
    p += self.size
    for i in range(1, self.log + 1):
      self.push(p >> i)
    return self.data[p]

  def prod(self, l, r):
    #assert 0 <= l <= r <= self.n
    if l == r: return self.e
    l += self.size
    r += self.size
    for i in range(1, self.log + 1)[::-1]:
      if ((l >> i) << i) != l: self.push(l >> i)
      if ((r >> i) << i) != r: self.push(r >> i)
    sml = smr = self.e
    while l < r:
      if l & 1:
        sml = self.op(sml, self.data[l])
        l += 1
      if r & 1:
        r -= 1
        smr = self.op(self.data[r], smr)
      l >>= 1
      r >>= 1
    return self.op(sml, smr)

  def apply(self, p, f):
    #assert 0 <= p < self.n
    p += self.size
    for i in range(1, self.log + 1)[::-1]:
      self.push(p >> i)
    self.data[p] = self.mapping(f, self.data[p])
    for i in range(1, self.log + 1):
      self.update(p >> i)

  def range_apply(self, l, r, f):
    #assert 0 <= l <= r <= self.n
    if l == r: return
    l += self.size
    r += self.size
    for i in range(1, self.log + 1)[::-1]:
      if ((l >> i) << i) != l: self.push(l >> i)
      if ((r >> i) << i) != r: self.push((r - 1) >> i)
    l2 = l
    r2 = r
    while l < r:
      if l & 1:
        self.all_apply(l, f)
        l += 1
      if r & 1:
        r -= 1
        self.all_apply(r, f)
      l >>= 1
      r >>= 1
    l = l2
    r = r2
    for i in range(1, self.log + 1):
      if ((l >> i) << i) != l: self.update(l >> i)
      if ((r >> i) << i) != r: self.update((r - 1) >> i)

  def max_right(self, l, g):
    #assert 0 <= l <= self.n
    #assert g(self.e)
    if l == self.n: return self.n
    l += self.size
    for i in range(1, self.log + 1)[::-1]:
      self.push(l >> i)
    sm = self.e
    while True:
      while l % 2 == 0: l >>= 1
      if not g(self.op(sm, self.data[l])):
        while l < self.size:
          self.push(l)
          l = 2 * l
          if g(self.op(sm, self.data[l])):
            sm = self.op(sm, self.data[l])
            l += 1
        return l - self.size
      sm = self.op(sm, self.data[l])
      l += 1
      if (l & -l) == l: return self.n

  def min_left(self, r, g):
    #assert 0 <= r <= self.n
    #assert g(self.e)
    if r == 0: return 0
    r += self.size
    for i in range(1, self.log + 1)[::-1]:
      self.push((r - 1) >> i)
    sm = self.e
    while True:
      r -= 1
      while r > 1 and r % 2: r >>= 1
      if not g(self.op(self.data[r], sm)):
        while r < self.size:
          self.push(r)
          r = 2 * r + 1
          if g(self.op(self.data[r], sm)):
            sm = self.op(self.data[r], sm)
            r -= 1
        return r + 1 - self.size
      sm = self.op(self.data[r], sm)
      if (r & -r) == r: return 0

import sys
input = sys.stdin.buffer.readline

INF = 10**18
MOD = 998244353

N, Q = map(int, input().split())

def op(x, y):
  xv, xr = x
  yv, yr = y
  return (xv + yv) % MOD, (xr + yr) % MOD

def mapping(p, x): #pが更新後の値, xが更新する前の値
  xv, xr = x
  if p != INF:
    return p * xr % MOD, xr
  return x

def composition(p, q):
  if p != INF:
    return p
  return q

lis = [[1,1] for _ in range(N)]
tmp = 1
for i in range(N-2,-1,-1):
  tmp *= 10 
  tmp %= MOD
  lis[i][0], lis[i][1] = tmp, tmp
#print(lis)
#arr = [(e,e) for e in lis]

e = 0
id = INF

lst = LazySegmentTree(N, op, e, mapping, composition, id)
lst.build(lis)

ans = [0]*Q

for i in range(Q):
  l, r, d = map(int, input().split())
  lst.range_apply(l - 1, r, d)
  ans[i] = lst.data[1][0]


print(*ans, sep='\n')

Buggy python submission (verdict: Time Limit Exceeded):
MOD = 998244353

#Ref = [0 for _ in range(100)]
#pow_2_lv = [pow(2,lv) for lv in range(100)]
#P = 3*10**5+1
#pow_10_MOD = [1 for i in range(P)]
#for i in range(1,P):
#  pow_10_MOD[i] = (pow_10_MOD[i-1] * 10) % MOD

#Ref[0] = 1
#for s in range(1,P.bit_length()):
#    Ref[s] += (Ref[s-1] * (pow_10_MOD[pow_2_lv[s-1]] + 1)) % MOD
#    Ref[s] %= MOD

#####ide_ele#####
ide_ele = [0, 0]
#################

#####segfunc#####
def segfunc(x, y):
    xa, xb = x
    ya, yb = y
    return [(xa + ya) % MOD, (xb + yb) % MOD]
#################

#####triv_act#####
triv_act = 0
#################

#####action#####
def action(x, p):
    if p != triv_act:
      _, xb = x
      return [(xb * p) % MOD , xb]
    return x
#################

class LazySegmentTree:
    """
    init(init_val, ide_ele): 配列init_valで初期化 O(N)
    update(l, r, x): 区間[l, r)をxに更新 O(logN)
    query(l, r): 区間[l, r)をsegfuncしたものを返す O(logN)
    """
    def __init__(self, init_val, segfunc, ide_ele, action, triv_act):
        """
        init_val: 配列の初期値
        segfunc: 区間にしたい操作
        action: モノイドへの作用
        ide_ele: 単位元
        triv_act: モノイドへ自明に作用する元
        num: n以上の最小の2のべき乗
        data: 値配列(1-index)
        lazy: 遅延配列(1-index)
        """
        n = len(init_val)
        self.segfunc = segfunc
        self.action = action
        self.ide_ele = ide_ele
        self.triv_act = triv_act
        self.num = 1 << (n - 1).bit_length()
        self.data = [ide_ele] * 2 * self.num
        self.lazy = [triv_act] * 2 * self.num
        # 配列の値を葉にセット
        for i in range(n):
            self.data[self.num + i] = init_val[i]
        # 構築していく
        for i in range(self.num - 1, 0, -1):
            self.data[i] = self.segfunc(self.data[2 * i], self.data[2 * i + 1])

    def gindex(self, l, r):
            """
            伝搬する対象の区間を求める
            lm: 伝搬する必要のある最大の左閉区間
            rm: 伝搬する必要のある最大の右開区間
            """
            l += self.num
            r += self.num
            lm = (l // (l & -l)) >> 1
            rm = (r // (r & -r)) >> 1

            while r > l > 0:
                if l <= lm:
                    yield l
                if r <= rm:
                    yield r
                r >>= 1
                l >>= 1
            while l > 0:
                yield l
                l >>= 1

    def propagates(self, *ids):
        """
        遅延伝搬処理
        ids: 伝搬する対象の区間 
        """
        for i in reversed(ids):
            v = self.lazy[i]
            if v == triv_act:
                continue
            lg = self.data[2 * i][1].bit_length()-1
            self.lazy[2 * i] = v
            self.lazy[2 * i + 1] = v
            self.data[2 * i] = action(self.data[2 * i], v)
            self.data[2 * i + 1] = action(self.data[2 * i + 1], v)
            self.lazy[i] = self.triv_act

    def update(self, l, r, x):
        """
        区間[l, r)の値をxに更新
        l, r: index(0-index)
        x: update value
        """
        *ids, = self.gindex(l, r)
        self.propagates(*ids)
        l += self.num
        r += self.num
        while l < r:
            if l & 1:
                self.lazy[l] = x
                self.data[l] = action(self.data[l], x)
                l += 1
            if r & 1:
                self.lazy[r - 1] = x
                self.data[r - 1] = action(self.data[r - 1], x)
            r >>= 1
            l >>= 1
        for i in ids:
            self.data[i] = self.segfunc(self.data[2 * i], self.data[2 * i + 1])


    def query(self, l, r):
        """
        [l, r)のsegfuncしたものを得る
        l: index(0-index)
        r: index(0-index)
        """
        *ids, = self.gindex(l, r)
        self.propagates(*ids)

        res_l = self.ide_ele
        res_r = self.ide_ele

        l += self.num
        r += self.num
        while l < r:
            if l & 1:
                res_l = self.segfunc(res_l, self.data[l])
                l += 1
            if r & 1:
                res_r = self.segfunc(self.data[r - 1], res_r)
            l >>= 1
            r >>= 1
        return self.segfunc(res_l, res_r)      
      
import sys
input = sys.stdin.buffer.readline

N, Q = map(int,input().split()) #3*10**5+5, 10**5 
init = [[1,1] for _ in range(N)]

tmp = 1
for i in range(N-2,-1,-1):
  tmp = (tmp * 10) % MOD
  init[i][0], init[i][1] = tmp, tmp
lst = LazySegmentTree(init, segfunc, ide_ele, action, triv_act)
#print(lst.data[1], lst.data)

for i in range(Q):
  L, R, D = map(int,input().split()) #i, N//2+1, i*4 % 9 + 1
  L -= 1
  R -= 1
  lst.update(L, R+1, D)
  #print(lst.data[1], lst.data)
  print(lst.data[1][0])
#print(lst.data)

Fixed submission:
class LazySegmentTree():
  def __init__(self, n, op, e, mapping, composition, id):
    self.n = n
    self.op = op
    self.e = e
    self.mapping = mapping
    self.composition = composition
    self.id = id
    self.log = (n - 1).bit_length()
    self.size = 1 << self.log
    self.data = [(e,e)] * (2 * self.size)
    self.lazy = [id] * (self.size)

  def update(self, k):
    self.data[k] = self.op(self.data[2 * k], self.data[2 * k + 1])

  def all_apply(self, k, f):
    self.data[k] = self.mapping(f, self.data[k])
    if k < self.size:
      self.lazy[k] = self.composition(f, self.lazy[k])

  def push(self, k):
    self.all_apply(2 * k, self.lazy[k])
    self.all_apply(2 * k + 1, self.lazy[k])
    self.lazy[k] = self.id

  def build(self, arr):
    #assert len(arr) == self.n
    for i, a in enumerate(arr):
      self.data[self.size + i] = a
    for i in range(self.size-1,0,-1):
      self.update(i)

  def set(self, p, x):
    #assert 0 <= p < self.n
    p += self.size
    for i in range(self.log,0,-1):
      self.push(p >> i)
    self.data[p] = x
    for i in range(1, self.log + 1):
      self.update(p >> i)

  def get(self, p):
    #assert 0 <= p < self.n
    p += self.size
    for i in range(1, self.log + 1):
      self.push(p >> i)
    return self.data[p]

  def prod(self, l, r):
    #assert 0 <= l <= r <= self.n
    if l == r: return self.e
    l += self.size
    r += self.size
    for i in range(1, self.log + 1)[::-1]:
      if ((l >> i) << i) != l: self.push(l >> i)
      if ((r >> i) << i) != r: self.push(r >> i)
    sml = smr = self.e
    while l < r:
      if l & 1:
        sml = self.op(sml, self.data[l])
        l += 1
      if r & 1:
        r -= 1
        smr = self.op(self.data[r], smr)
      l >>= 1
      r >>= 1
    return self.op(sml, smr)

  def apply(self, p, f):
    #assert 0 <= p < self.n
    p += self.size
    for i in range(1, self.log + 1)[::-1]:
      self.push(p >> i)
    self.data[p] = self.mapping(f, self.data[p])
    for i in range(1, self.log + 1):
      self.update(p >> i)

  def range_apply(self, l, r, f):
    #assert 0 <= l <= r <= self.n
    if l == r: return
    l += self.size
    r += self.size
    for i in range(1, self.log + 1)[::-1]:
      if ((l >> i) << i) != l: self.push(l >> i)
      if ((r >> i) << i) != r: self.push((r - 1) >> i)
    l2 = l
    r2 = r
    while l < r:
      if l & 1:
        self.all_apply(l, f)
        l += 1
      if r & 1:
        r -= 1
        self.all_apply(r, f)
      l >>= 1
      r >>= 1
    l = l2
    r = r2
    for i in range(1, self.log + 1):
      if ((l >> i) << i) != l: self.update(l >> i)
      if ((r >> i) << i) != r: self.update((r - 1) >> i)

  def max_right(self, l, g):
    #assert 0 <= l <= self.n
    #assert g(self.e)
    if l == self.n: return self.n
    l += self.size
    for i in range(1, self.log + 1)[::-1]:
      self.push(l >> i)
    sm = self.e
    while True:
      while l % 2 == 0: l >>= 1
      if not g(self.op(sm, self.data[l])):
        while l < self.size:
          self.push(l)
          l = 2 * l
          if g(self.op(sm, self.data[l])):
            sm = self.op(sm, self.data[l])
            l += 1
        return l - self.size
      sm = self.op(sm, self.data[l])
      l += 1
      if (l & -l) == l: return self.n

  def min_left(self, r, g):
    #assert 0 <= r <= self.n
    #assert g(self.e)
    if r == 0: return 0
    r += self.size
    for i in range(1, self.log + 1)[::-1]:
      self.push((r - 1) >> i)
    sm = self.e
    while True:
      r -= 1
      while r > 1 and r % 2: r >>= 1
      if not g(self.op(self.data[r], sm)):
        while r < self.size:
          self.push(r)
          r = 2 * r + 1
          if g(self.op(self.data[r], sm)):
            sm = self.op(self.data[r], sm)
            r -= 1
        return r + 1 - self.size
      sm = self.op(self.data[r], sm)
      if (r & -r) == r: return 0

import sys
input = sys.stdin.buffer.readline

INF = 10**18
MOD = 998244353

N, Q = map(int, input().split())

def op(x, y):
  xv, xr = x
  yv, yr = y
  return (xv + yv) % MOD, (xr + yr) % MOD

def mapping(p, x): #pが更新後の値, xが更新する前の値
  xv, xr = x
  if p != INF:
    return p * xr % MOD, xr
  return x

def composition(p, q):
  if p != INF:
    return p
  return q

lis = [[1,1] for _ in range(N)]
tmp = 1
for i in range(N-2,-1,-1):
  tmp *= 10 
  tmp %= MOD
  lis[i][0], lis[i][1] = tmp, tmp
#print(lis)
#arr = [(e,e) for e in lis]

e = 0
id = INF

lst = LazySegmentTree(N, op, e, mapping, composition, id)
lst.build(lis)

ans = [0]*Q

for i in range(Q):
  l, r, d = map(int, input().split())
  lst.range_apply(l - 1, r, d)
  ans[i] = lst.data[1][0]


print(*ans, sep='\n')

Buggy python submission (verdict: Time Limit Exceeded):
MOD = 998244353

#Ref = [0 for _ in range(100)]
#pow_2_lv = [pow(2,lv) for lv in range(100)]
#P = 3*10**5+1
#pow_10_MOD = [1 for i in range(P)]
#for i in range(1,P):
#  pow_10_MOD[i] = (pow_10_MOD[i-1] * 10) % MOD

#Ref[0] = 1
#for s in range(1,P.bit_length()):
#    Ref[s] += (Ref[s-1] * (pow_10_MOD[pow_2_lv[s-1]] + 1)) % MOD
#    Ref[s] %= MOD

#####ide_ele#####
ide_ele = [0, 0]
#################

#####segfunc#####
def segfunc(x, y):
    xa, xb = x
    ya, yb = y
    return [(xa + ya) % MOD, (xb + yb) % MOD]
#################

#####triv_act#####
triv_act = 0
#################

#####action#####
def action(x, p):
    if p != triv_act:
      _, xb = x
      return [(xb * p) % MOD , xb]
    return x
#################

class LazySegmentTree:
    """
    init(init_val, ide_ele): 配列init_valで初期化 O(N)
    update(l, r, x): 区間[l, r)をxに更新 O(logN)
    query(l, r): 区間[l, r)をsegfuncしたものを返す O(logN)
    """
    def __init__(self, init_val, segfunc, ide_ele, action, triv_act):
        """
        init_val: 配列の初期値
        segfunc: 区間にしたい操作
        action: モノイドへの作用
        ide_ele: 単位元
        triv_act: モノイドへ自明に作用する元
        num: n以上の最小の2のべき乗
        data: 値配列(1-index)
        lazy: 遅延配列(1-index)
        """
        n = len(init_val)
        self.segfunc = segfunc
        self.action = action
        self.ide_ele = ide_ele
        self.triv_act = triv_act
        self.num = 1 << (n - 1).bit_length()
        self.data = [ide_ele] * 2 * self.num
        self.lazy = [triv_act] * 2 * self.num
        # 配列の値を葉にセット
        for i in range(n):
            self.data[self.num + i] = init_val[i]
        # 構築していく
        for i in range(self.num - 1, 0, -1):
            self.data[i] = self.segfunc(self.data[2 * i], self.data[2 * i + 1])

    def gindex(self, l, r):
            """
            伝搬する対象の区間を求める
            lm: 伝搬する必要のある最大の左閉区間
            rm: 伝搬する必要のある最大の右開区間
            """
            l += self.num
            r += self.num
            lm = (l // (l & -l)) >> 1
            rm = (r // (r & -r)) >> 1

            while r > l > 0:
                if l <= lm:
                    yield l
                if r <= rm:
                    yield r
                r >>= 1
                l >>= 1
            while l > 0:
                yield l
                l >>= 1

    def propagates(self, *ids):
        """
        遅延伝搬処理
        ids: 伝搬する対象の区間 
        """
        for i in reversed(ids):
            v = self.lazy[i]
            if v == triv_act:
                continue
            lg = self.data[2 * i][1].bit_length()-1
            self.lazy[2 * i] = v
            self.lazy[2 * i + 1] = v
            self.data[2 * i] = action(self.data[2 * i], v)
            self.data[2 * i + 1] = action(self.data[2 * i + 1], v)
            self.lazy[i] = self.triv_act

    def update(self, l, r, x):
        """
        区間[l, r)の値をxに更新
        l, r: index(0-index)
        x: update value
        """
        *ids, = self.gindex(l, r)
        self.propagates(*ids)
        l += self.num
        r += self.num
        while l < r:
            if l & 1:
                self.lazy[l] = x
                self.data[l] = action(self.data[l], x)
                l += 1
            if r & 1:
                self.lazy[r - 1] = x
                self.data[r - 1] = action(self.data[r - 1], x)
            r >>= 1
            l >>= 1
        for i in ids:
            self.data[i] = self.segfunc(self.data[2 * i], self.data[2 * i + 1])


    def query(self, l, r):
        """
        [l, r)のsegfuncしたものを得る
        l: index(0-index)
        r: index(0-index)
        """
        *ids, = self.gindex(l, r)
        self.propagates(*ids)

        res_l = self.ide_ele
        res_r = self.ide_ele

        l += self.num
        r += self.num
        while l < r:
            if l & 1:
                res_l = self.segfunc(res_l, self.data[l])
                l += 1
            if r & 1:
                res_r = self.segfunc(self.data[r - 1], res_r)
            l >>= 1
            r >>= 1
        return self.segfunc(res_l, res_r)      
      
import sys
input = sys.stdin.readline

N, Q = map(int,input().split()) #3*10**5+5, 10**5 
init = [[1,1] for _ in range(N)]

tmp = 1
for i in range(N-2,-1,-1):
  tmp = (tmp * 10) % MOD
  init[i][0], init[i][1] = tmp, tmp
lst = LazySegmentTree(init, segfunc, ide_ele, action, triv_act)
#print(lst.data[1], lst.data)

for i in range(Q):
  L, R, D = map(int,input().split()) #i, N//2+1, i*4 % 9 + 1
  L -= 1
  R -= 1
  lst.update(L, R+1, D)
  #print(lst.data[1], lst.data)
  print(lst.data[1][0])
#print(lst.data)

Fixed submission:
class LazySegmentTree():
  def __init__(self, n, op, e, mapping, composition, id):
    self.n = n
    self.op = op
    self.e = e
    self.mapping = mapping
    self.composition = composition
    self.id = id
    self.log = (n - 1).bit_length()
    self.size = 1 << self.log
    self.data = [(e,e)] * (2 * self.size)
    self.lazy = [id] * (self.size)

  def update(self, k):
    self.data[k] = self.op(self.data[2 * k], self.data[2 * k + 1])

  def all_apply(self, k, f):
    self.data[k] = self.mapping(f, self.data[k])
    if k < self.size:
      self.lazy[k] = self.composition(f, self.lazy[k])

  def push(self, k):
    self.all_apply(2 * k, self.lazy[k])
    self.all_apply(2 * k + 1, self.lazy[k])
    self.lazy[k] = self.id

  def build(self, arr):
    #assert len(arr) == self.n
    for i, a in enumerate(arr):
      self.data[self.size + i] = a
    for i in range(self.size-1,0,-1):
      self.update(i)

  def set(self, p, x):
    #assert 0 <= p < self.n
    p += self.size
    for i in range(self.log,0,-1):
      self.push(p >> i)
    self.data[p] = x
    for i in range(1, self.log + 1):
      self.update(p >> i)

  def get(self, p):
    #assert 0 <= p < self.n
    p += self.size
    for i in range(1, self.log + 1):
      self.push(p >> i)
    return self.data[p]

  def prod(self, l, r):
    #assert 0 <= l <= r <= self.n
    if l == r: return self.e
    l += self.size
    r += self.size
    for i in range(1, self.log + 1)[::-1]:
      if ((l >> i) << i) != l: self.push(l >> i)
      if ((r >> i) << i) != r: self.push(r >> i)
    sml = smr = self.e
    while l < r:
      if l & 1:
        sml = self.op(sml, self.data[l])
        l += 1
      if r & 1:
        r -= 1
        smr = self.op(self.data[r], smr)
      l >>= 1
      r >>= 1
    return self.op(sml, smr)

  def apply(self, p, f):
    #assert 0 <= p < self.n
    p += self.size
    for i in range(1, self.log + 1)[::-1]:
      self.push(p >> i)
    self.data[p] = self.mapping(f, self.data[p])
    for i in range(1, self.log + 1):
      self.update(p >> i)

  def range_apply(self, l, r, f):
    #assert 0 <= l <= r <= self.n
    if l == r: return
    l += self.size
    r += self.size
    for i in range(1, self.log + 1)[::-1]:
      if ((l >> i) << i) != l: self.push(l >> i)
      if ((r >> i) << i) != r: self.push((r - 1) >> i)
    l2 = l
    r2 = r
    while l < r:
      if l & 1:
        self.all_apply(l, f)
        l += 1
      if r & 1:
        r -= 1
        self.all_apply(r, f)
      l >>= 1
      r >>= 1
    l = l2
    r = r2
    for i in range(1, self.log + 1):
      if ((l >> i) << i) != l: self.update(l >> i)
      if ((r >> i) << i) != r: self.update((r - 1) >> i)

  def max_right(self, l, g):
    #assert 0 <= l <= self.n
    #assert g(self.e)
    if l == self.n: return self.n
    l += self.size
    for i in range(1, self.log + 1)[::-1]:
      self.push(l >> i)
    sm = self.e
    while True:
      while l % 2 == 0: l >>= 1
      if not g(self.op(sm, self.data[l])):
        while l < self.size:
          self.push(l)
          l = 2 * l
          if g(self.op(sm, self.data[l])):
            sm = self.op(sm, self.data[l])
            l += 1
        return l - self.size
      sm = self.op(sm, self.data[l])
      l += 1
      if (l & -l) == l: return self.n

  def min_left(self, r, g):
    #assert 0 <= r <= self.n
    #assert g(self.e)
    if r == 0: return 0
    r += self.size
    for i in range(1, self.log + 1)[::-1]:
      self.push((r - 1) >> i)
    sm = self.e
    while True:
      r -= 1
      while r > 1 and r % 2: r >>= 1
      if not g(self.op(self.data[r], sm)):
        while r < self.size:
          self.push(r)
          r = 2 * r + 1
          if g(self.op(self.data[r], sm)):
            sm = self.op(self.data[r], sm)
            r -= 1
        return r + 1 - self.size
      sm = self.op(self.data[r], sm)
      if (r & -r) == r: return 0

import sys
input = sys.stdin.buffer.readline

INF = 10**18
MOD = 998244353

N, Q = map(int, input().split())

def op(x, y):
  xv, xr = x
  yv, yr = y
  return (xv + yv) % MOD, (xr + yr) % MOD

def mapping(p, x): #pが更新後の値, xが更新する前の値
  xv, xr = x
  if p != INF:
    return p * xr % MOD, xr
  return x

def composition(p, q):
  if p != INF:
    return p
  return q

lis = [[1,1] for _ in range(N)]
tmp = 1
for i in range(N-2,-1,-1):
  tmp *= 10 
  tmp %= MOD
  lis[i][0], lis[i][1] = tmp, tmp
#print(lis)
#arr = [(e,e) for e in lis]

e = 0
id = INF

lst = LazySegmentTree(N, op, e, mapping, composition, id)
lst.build(lis)

ans = [0]*Q

for i in range(Q):
  l, r, d = map(int, input().split())
  lst.range_apply(l - 1, r, d)
  ans[i] = lst.data[1][0]


print(*ans, sep='\n')

Buggy python submission (verdict: Time Limit Exceeded):
MOD = 998244353

Ref = [0 for _ in range(100)]
pow_2_lv = [pow(2,lv) for lv in range(100)]
P = 3*10**5+1
pow_10_MOD = [1 for i in range(P)]
for i in range(1,P):
  pow_10_MOD[i] = (pow_10_MOD[i-1] * 10) % MOD

Ref[0] = 1
for s in range(1,P.bit_length()):
    Ref[s] += (Ref[s-1] * (pow_10_MOD[pow_2_lv[s-1]] + 1)) % MOD
    Ref[s] %= MOD

#print(Ref[0:10])    
#####segfunc#####
def segfunc(x, y):
    return [(x[0] * pow_10_MOD[y[1]] + y[0]) % MOD, x[1] + y[1]]
#################

#####ide_ele#####
ide_ele = [0, 0]
#################

class LazySegmentTree:
    """
    init(init_val, ide_ele): 配列init_valで初期化 O(N)
    update(l, r, x): 区間[l, r)をxに更新 O(logN)
    query(l, r): 区間[l, r)をsegfuncしたものを返す O(logN)
    """
    def __init__(self, init_val, segfunc, ide_ele):
        """
        init_val: 配列の初期値
        segfunc: 区間にしたい操作
        ide_ele: 単位元
        num: n以上の最小の2のべき乗
        data: 値配列(1-index)
        lazy: 遅延配列(1-index)
        """
        n = len(init_val)
        self.segfunc = segfunc
        self.ide_ele = ide_ele
        self.num = 1 << (n - 1).bit_length()
        self.data = [ide_ele] * 2 * self.num
        self.lazy = [0] * 2 * self.num
        # 配列の値を葉にセット
        for i in range(n):
            self.data[self.num + i] = init_val[i]
        # 構築していく
        for i in range(self.num - 1, 0, -1):
            self.data[i] = self.segfunc(self.data[2 * i], self.data[2 * i + 1])

    def gindex(self, l, r):
            """
            伝搬する対象の区間を求める
            lm: 伝搬する必要のある最大の左閉区間
            rm: 伝搬する必要のある最大の右開区間
            """
            l += self.num
            r += self.num
            lm = (l // (l & -l)) >> 1
            rm = (r // (r & -r)) >> 1

            while r > l:
                if l <= lm:
                    yield l
                if r <= rm:
                    yield r
                r >>= 1
                l >>= 1
            while l > 0:
                yield l
                l >>= 1

    def propagates(self, *ids):
        """
        遅延伝搬処理
        ids: 伝搬する対象の区間 
        """
        for i in reversed(ids):
            v = self.lazy[i]
            if v == 0:
                continue
            lg = self.data[2 * i][1].bit_length()-1
            self.lazy[2 * i] = v
            self.lazy[2 * i + 1] = v
            self.data[2 * i][0] = (v * Ref[lg]) % MOD
            self.data[2 * i + 1][0] = (v * Ref[lg]) % MOD
            self.lazy[i] = 0

    def update(self, l, r, x):
        """
        区間[l, r)の値をxに更新
        l, r: index(0-index)
        x: update value
        """
        *ids, = self.gindex(l, r)
        self.propagates(*ids)
        l += self.num
        r += self.num
        while l < r:
            if l & 1:
                lg = self.data[l][1].bit_length()-1
                self.lazy[l] = x
                self.data[l][0] = (x * Ref[lg]) % MOD
                l += 1
            if r & 1:
                lg = self.data[r - 1][1].bit_length()-1
                self.lazy[r - 1] = x
                self.data[r - 1][0] = (x * Ref[lg]) % MOD
            r >>= 1
            l >>= 1
        for i in ids:
            self.data[i] = self.segfunc(self.data[2 * i], self.data[2 * i + 1])


    def query(self, l, r):
        """
        [l, r)のsegfuncしたものを得る
        l: index(0-index)
        r: index(0-index)
        """
        *ids, = self.gindex(l, r)
        self.propagates(*ids)

        res_l = self.ide_ele
        res_r = self.ide_ele

        l += self.num
        r += self.num
        while l < r:
            if l & 1:
                res_l = self.segfunc(res_l, self.data[l])
                l += 1
            if r & 1:
                res_r = self.segfunc(self.data[r - 1], res_r)
            l >>= 1
            r >>= 1
        return self.segfunc(res_l, res_r)
      
      
import sys
input = sys.stdin.readline

N, Q = map(int,input().split()) #3*10**5+5, 10**5 
lst = LazySegmentTree([[1, 1] for _ in range(N)], segfunc, ide_ele)
#print(lst.data[1], lst.data)

for i in range(Q):
  L, R, D = map(int,input().split()) #i, N//2+1, i*4 % 9 + 1
  L -= 1
  R -= 1
  lst.update(L, R+1, D)
  #print(lst.data[1], lst.data)
  print(lst.data[1][0])


Fixed submission:
class LazySegmentTree():
  def __init__(self, n, op, e, mapping, composition, id):
    self.n = n
    self.op = op
    self.e = e
    self.mapping = mapping
    self.composition = composition
    self.id = id
    self.log = (n - 1).bit_length()
    self.size = 1 << self.log
    self.data = [(e,e)] * (2 * self.size)
    self.lazy = [id] * (self.size)

  def update(self, k):
    self.data[k] = self.op(self.data[2 * k], self.data[2 * k + 1])

  def all_apply(self, k, f):
    self.data[k] = self.mapping(f, self.data[k])
    if k < self.size:
      self.lazy[k] = self.composition(f, self.lazy[k])

  def push(self, k):
    self.all_apply(2 * k, self.lazy[k])
    self.all_apply(2 * k + 1, self.lazy[k])
    self.lazy[k] = self.id

  def build(self, arr):
    #assert len(arr) == self.n
    for i, a in enumerate(arr):
      self.data[self.size + i] = a
    for i in range(self.size-1,0,-1):
      self.update(i)

  def set(self, p, x):
    #assert 0 <= p < self.n
    p += self.size
    for i in range(self.log,0,-1):
      self.push(p >> i)
    self.data[p] = x
    for i in range(1, self.log + 1):
      self.update(p >> i)

  def get(self, p):
    #assert 0 <= p < self.n
    p += self.size
    for i in range(1, self.log + 1):
      self.push(p >> i)
    return self.data[p]

  def prod(self, l, r):
    #assert 0 <= l <= r <= self.n
    if l == r: return self.e
    l += self.size
    r += self.size
    for i in range(1, self.log + 1)[::-1]:
      if ((l >> i) << i) != l: self.push(l >> i)
      if ((r >> i) << i) != r: self.push(r >> i)
    sml = smr = self.e
    while l < r:
      if l & 1:
        sml = self.op(sml, self.data[l])
        l += 1
      if r & 1:
        r -= 1
        smr = self.op(self.data[r], smr)
      l >>= 1
      r >>= 1
    return self.op(sml, smr)

  def apply(self, p, f):
    #assert 0 <= p < self.n
    p += self.size
    for i in range(1, self.log + 1)[::-1]:
      self.push(p >> i)
    self.data[p] = self.mapping(f, self.data[p])
    for i in range(1, self.log + 1):
      self.update(p >> i)

  def range_apply(self, l, r, f):
    #assert 0 <= l <= r <= self.n
    if l == r: return
    l += self.size
    r += self.size
    for i in range(1, self.log + 1)[::-1]:
      if ((l >> i) << i) != l: self.push(l >> i)
      if ((r >> i) << i) != r: self.push((r - 1) >> i)
    l2 = l
    r2 = r
    while l < r:
      if l & 1:
        self.all_apply(l, f)
        l += 1
      if r & 1:
        r -= 1
        self.all_apply(r, f)
      l >>= 1
      r >>= 1
    l = l2
    r = r2
    for i in range(1, self.log + 1):
      if ((l >> i) << i) != l: self.update(l >> i)
      if ((r >> i) << i) != r: self.update((r - 1) >> i)

  def max_right(self, l, g):
    #assert 0 <= l <= self.n
    #assert g(self.e)
    if l == self.n: return self.n
    l += self.size
    for i in range(1, self.log + 1)[::-1]:
      self.push(l >> i)
    sm = self.e
    while True:
      while l % 2 == 0: l >>= 1
      if not g(self.op(sm, self.data[l])):
        while l < self.size:
          self.push(l)
          l = 2 * l
          if g(self.op(sm, self.data[l])):
            sm = self.op(sm, self.data[l])
            l += 1
        return l - self.size
      sm = self.op(sm, self.data[l])
      l += 1
      if (l & -l) == l: return self.n

  def min_left(self, r, g):
    #assert 0 <= r <= self.n
    #assert g(self.e)
    if r == 0: return 0
    r += self.size
    for i in range(1, self.log + 1)[::-1]:
      self.push((r - 1) >> i)
    sm = self.e
    while True:
      r -= 1
      while r > 1 and r % 2: r >>= 1
      if not g(self.op(self.data[r], sm)):
        while r < self.size:
          self.push(r)
          r = 2 * r + 1
          if g(self.op(self.data[r], sm)):
            sm = self.op(self.data[r], sm)
            r -= 1
        return r + 1 - self.size
      sm = self.op(self.data[r], sm)
      if (r & -r) == r: return 0

import sys
input = sys.stdin.buffer.readline

INF = 10**18
MOD = 998244353

N, Q = map(int, input().split())

def op(x, y):
  xv, xr = x
  yv, yr = y
  return (xv + yv) % MOD, (xr + yr) % MOD

def mapping(p, x): #pが更新後の値, xが更新する前の値
  xv, xr = x
  if p != INF:
    return p * xr % MOD, xr
  return x

def composition(p, q):
  if p != INF:
    return p
  return q

lis = [[1,1] for _ in range(N)]
tmp = 1
for i in range(N-2,-1,-1):
  tmp *= 10 
  tmp %= MOD
  lis[i][0], lis[i][1] = tmp, tmp
#print(lis)
#arr = [(e,e) for e in lis]

e = 0
id = INF

lst = LazySegmentTree(N, op, e, mapping, composition, id)
lst.build(lis)

ans = [0]*Q

for i in range(Q):
  l, r, d = map(int, input().split())
  lst.range_apply(l - 1, r, d)
  ans[i] = lst.data[1][0]


print(*ans, sep='\n')

Buggy python submission (verdict: Runtime Error):
MOD = 998244353

Ref = [[0 for _ in range(100)] for _ in range(10)]
pow_2_lv = [pow(2,lv) for lv in range(30)]
P = 3*10**5+1
pow_10_MOD = [1 for i in range(P)]
for i in range(1,P):
  pow_10_MOD[i] = (pow_10_MOD[i-1] * 10) % MOD

for i in range(1,10):
  Ref[i][0] = i
  for s in range(1,30):
    Ref[i][s] += (Ref[i][s-1] * (pow(10, pow_2_lv[s-1], MOD) + 1)) % MOD
    Ref[i][s] %= MOD
#print(Ref)  
#####segfunc#####
def segfunc(x, y):
    return [(x[0] * pow_10_MOD[y[1]] + y[0]) % MOD, x[1] + y[1]]
#################

#####ide_ele#####
ide_ele = [0,0]
#################

class LazySegmentTree:
    """
    init(init_val, ide_ele): 配列init_valで初期化 O(N)
    update(l, r, x): 区間[l, r)をxに更新 O(logN)
    query(l, r): 区間[l, r)をsegfuncしたものを返す O(logN)
    """
    def __init__(self, init_val, segfunc, ide_ele):
        """
        init_val: 配列の初期値
        segfunc: 区間にしたい操作
        ide_ele: 単位元
        num: n以上の最小の2のべき乗
        data: 値配列(1-index)
        lazy: 遅延配列(1-index)
        """
        n = len(init_val)
        self.segfunc = segfunc
        self.ide_ele = ide_ele
        self.num = 1 << (n - 1).bit_length()
        self.data = [ide_ele] * 2 * self.num
        self.lazy = [ide_ele] * 2 * self.num
        # 配列の値を葉にセット
        for i in range(n):
            self.data[self.num + i] = init_val[i]
        # 構築していく
        for i in range(self.num - 1, 0, -1):
            self.data[i] = self.segfunc(self.data[2 * i], self.data[2 * i + 1])

    def gindex(self, l, r):
            """
            伝搬する対象の区間を求める
            lm: 伝搬する必要のある最大の左閉区間
            rm: 伝搬する必要のある最大の右開区間
            """
            l += self.num
            r += self.num
            lm = l >> (l & -l).bit_length()
            rm = r >> (r & -r).bit_length()

            while r > l:
                if l <= lm:
                    yield l
                if r <= rm:
                    yield r
                r >>= 1
                l >>= 1
            while l:
                yield l
                l >>= 1

    def propagates(self, *ids):
        """
        遅延伝搬処理
        ids: 伝搬する対象の区間 
        """
        for i in reversed(ids):
            v = self.lazy[i]
            if v == ide_ele:
                continue
            lv = self.num.bit_length() - (2*i).bit_length() 
            upd = Ref[v[0]%10][lv]
            #print(v[0]%10, lv)
            self.lazy[2 * i] = [upd, pow_2_lv[lv]]
            self.lazy[2 * i + 1] = [upd, pow_2_lv[lv]]
            self.data[2 * i] = [upd, pow_2_lv[lv]]
            self.data[2 * i + 1] = [upd, pow_2_lv[lv]]
            self.lazy[i] = ide_ele

    def update(self, l, r, x):
        """
        区間[l, r)の値をxに更新
        l, r: index(0-index)
        x: update value
        """
        *ids, = self.gindex(l, r)
        #print(l, r, " ids = ", ids)
        self.propagates(*ids)
        l += self.num
        r += self.num
        while l < r:
            if l & 1:
                lv = self.num.bit_length() - l.bit_length() 
                #print(l, lv)
                upd = Ref[x[0]][lv]
                self.lazy[l] = [upd, pow_2_lv[lv]]
                self.data[l] = [upd, pow_2_lv[lv]]
                l += 1
            if r & 1:
                lv = self.num.bit_length() - (r-1).bit_length()
                upd = Ref[x[0]][lv]
                #print(r, lv)
                self.lazy[r - 1] = [upd, pow_2_lv[lv]]
                self.data[r - 1] = [upd, pow_2_lv[lv]]
            r >>= 1
            l >>= 1
        for i in ids:
            self.data[i] = self.segfunc(self.data[2 * i], self.data[2 * i + 1])


    def query(self, l, r):
        """
        [l, r)のsegfuncしたものを得る
        l: index(0-index)
        r: index(0-index)
        """
        *ids, = self.gindex(l, r)
        self.propagates(*ids)

        res = self.ide_ele

        l += self.num
        r += self.num
        while l < r:
            if l & 1:
                res = self.segfunc(self.data[l], res)
                l += 1
            if r & 1:
                res = self.segfunc(res, self.data[r - 1])
            l >>= 1
            r >>= 1
        return res
  
N, Q = 3*10**5, 10**5 #map(int,input().split())
lst = LazySegmentTree([[1, 1] for _ in range(N)], segfunc, ide_ele)
#print(lst.data)
for i in range(Q):
  L, R, D = i, N-i, i**2 % 9 + 1 #map(int,input().split())
  L -= 1
  R -= 1
  lst.update(L, R+1, [D,1])
  print(lst.data[1][0])


Fixed submission:
class LazySegmentTree():
  def __init__(self, n, op, e, mapping, composition, id):
    self.n = n
    self.op = op
    self.e = e
    self.mapping = mapping
    self.composition = composition
    self.id = id
    self.log = (n - 1).bit_length()
    self.size = 1 << self.log
    self.data = [(e,e)] * (2 * self.size)
    self.lazy = [id] * (self.size)

  def update(self, k):
    self.data[k] = self.op(self.data[2 * k], self.data[2 * k + 1])

  def all_apply(self, k, f):
    self.data[k] = self.mapping(f, self.data[k])
    if k < self.size:
      self.lazy[k] = self.composition(f, self.lazy[k])

  def push(self, k):
    self.all_apply(2 * k, self.lazy[k])
    self.all_apply(2 * k + 1, self.lazy[k])
    self.lazy[k] = self.id

  def build(self, arr):
    #assert len(arr) == self.n
    for i, a in enumerate(arr):
      self.data[self.size + i] = a
    for i in range(self.size-1,0,-1):
      self.update(i)

  def set(self, p, x):
    #assert 0 <= p < self.n
    p += self.size
    for i in range(self.log,0,-1):
      self.push(p >> i)
    self.data[p] = x
    for i in range(1, self.log + 1):
      self.update(p >> i)

  def get(self, p):
    #assert 0 <= p < self.n
    p += self.size
    for i in range(1, self.log + 1):
      self.push(p >> i)
    return self.data[p]

  def prod(self, l, r):
    #assert 0 <= l <= r <= self.n
    if l == r: return self.e
    l += self.size
    r += self.size
    for i in range(1, self.log + 1)[::-1]:
      if ((l >> i) << i) != l: self.push(l >> i)
      if ((r >> i) << i) != r: self.push(r >> i)
    sml = smr = self.e
    while l < r:
      if l & 1:
        sml = self.op(sml, self.data[l])
        l += 1
      if r & 1:
        r -= 1
        smr = self.op(self.data[r], smr)
      l >>= 1
      r >>= 1
    return self.op(sml, smr)

  def apply(self, p, f):
    #assert 0 <= p < self.n
    p += self.size
    for i in range(1, self.log + 1)[::-1]:
      self.push(p >> i)
    self.data[p] = self.mapping(f, self.data[p])
    for i in range(1, self.log + 1):
      self.update(p >> i)

  def range_apply(self, l, r, f):
    #assert 0 <= l <= r <= self.n
    if l == r: return
    l += self.size
    r += self.size
    for i in range(1, self.log + 1)[::-1]:
      if ((l >> i) << i) != l: self.push(l >> i)
      if ((r >> i) << i) != r: self.push((r - 1) >> i)
    l2 = l
    r2 = r
    while l < r:
      if l & 1:
        self.all_apply(l, f)
        l += 1
      if r & 1:
        r -= 1
        self.all_apply(r, f)
      l >>= 1
      r >>= 1
    l = l2
    r = r2
    for i in range(1, self.log + 1):
      if ((l >> i) << i) != l: self.update(l >> i)
      if ((r >> i) << i) != r: self.update((r - 1) >> i)

  def max_right(self, l, g):
    #assert 0 <= l <= self.n
    #assert g(self.e)
    if l == self.n: return self.n
    l += self.size
    for i in range(1, self.log + 1)[::-1]:
      self.push(l >> i)
    sm = self.e
    while True:
      while l % 2 == 0: l >>= 1
      if not g(self.op(sm, self.data[l])):
        while l < self.size:
          self.push(l)
          l = 2 * l
          if g(self.op(sm, self.data[l])):
            sm = self.op(sm, self.data[l])
            l += 1
        return l - self.size
      sm = self.op(sm, self.data[l])
      l += 1
      if (l & -l) == l: return self.n

  def min_left(self, r, g):
    #assert 0 <= r <= self.n
    #assert g(self.e)
    if r == 0: return 0
    r += self.size
    for i in range(1, self.log + 1)[::-1]:
      self.push((r - 1) >> i)
    sm = self.e
    while True:
      r -= 1
      while r > 1 and r % 2: r >>= 1
      if not g(self.op(self.data[r], sm)):
        while r < self.size:
          self.push(r)
          r = 2 * r + 1
          if g(self.op(self.data[r], sm)):
            sm = self.op(self.data[r], sm)
            r -= 1
        return r + 1 - self.size
      sm = self.op(self.data[r], sm)
      if (r & -r) == r: return 0

import sys
input = sys.stdin.buffer.readline

INF = 10**18
MOD = 998244353

N, Q = map(int, input().split())

def op(x, y):
  xv, xr = x
  yv, yr = y
  return (xv + yv) % MOD, (xr + yr) % MOD

def mapping(p, x): #pが更新後の値, xが更新する前の値
  xv, xr = x
  if p != INF:
    return p * xr % MOD, xr
  return x

def composition(p, q):
  if p != INF:
    return p
  return q

lis = [[1,1] for _ in range(N)]
tmp = 1
for i in range(N-2,-1,-1):
  tmp *= 10 
  tmp %= MOD
  lis[i][0], lis[i][1] = tmp, tmp
#print(lis)
#arr = [(e,e) for e in lis]

e = 0
id = INF

lst = LazySegmentTree(N, op, e, mapping, composition, id)
lst.build(lis)

ans = [0]*Q

for i in range(Q):
  l, r, d = map(int, input().split())
  lst.range_apply(l - 1, r, d)
  ans[i] = lst.data[1][0]


print(*ans, sep='\n')

Buggy python submission (verdict: Time Limit Exceeded):
MOD = 998244353

Ref = [[0 for _ in range(100)] for _ in range(10)]
pow_2_lv = [pow(2,lv) for lv in range(30)]
P = 3*10**5+1
pow_10_MOD = [1 for i in range(P)]
for i in range(1,P):
  pow_10_MOD[i] = (pow_10_MOD[i-1] * 10) % MOD

for i in range(1,10):
  Ref[i][0] = i
  for s in range(1,30):
    Ref[i][s] += (Ref[i][s-1] * (pow(10, pow_2_lv[s-1], MOD) + 1)) % MOD
    Ref[i][s] %= MOD
#print(Ref)  
#####segfunc#####
def segfunc(x, y):
    return [(x[0] * pow_10_MOD[y[1]] + y[0]) % MOD, x[1] + y[1]]
#################

#####ide_ele#####
ide_ele = [0,0]
#################

class LazySegmentTree:
    """
    init(init_val, ide_ele): 配列init_valで初期化 O(N)
    update(l, r, x): 区間[l, r)をxに更新 O(logN)
    query(l, r): 区間[l, r)をsegfuncしたものを返す O(logN)
    """
    def __init__(self, init_val, segfunc, ide_ele):
        """
        init_val: 配列の初期値
        segfunc: 区間にしたい操作
        ide_ele: 単位元
        num: n以上の最小の2のべき乗
        data: 値配列(1-index)
        lazy: 遅延配列(1-index)
        """
        n = len(init_val)
        self.segfunc = segfunc
        self.ide_ele = ide_ele
        self.num = 1 << (n - 1).bit_length()
        self.data = [ide_ele] * 2 * self.num
        self.lazy = [ide_ele] * 2 * self.num
        # 配列の値を葉にセット
        for i in range(n):
            self.data[self.num + i] = init_val[i]
        # 構築していく
        for i in range(self.num - 1, 0, -1):
            self.data[i] = self.segfunc(self.data[2 * i], self.data[2 * i + 1])

    def gindex(self, l, r):
            """
            伝搬する対象の区間を求める
            lm: 伝搬する必要のある最大の左閉区間
            rm: 伝搬する必要のある最大の右開区間
            """
            l += self.num
            r += self.num
            lm = l >> (l & -l).bit_length()
            rm = r >> (r & -r).bit_length()

            while r > l:
                if l <= lm:
                    yield l
                if r <= rm:
                    yield r
                r >>= 1
                l >>= 1
            while l:
                yield l
                l >>= 1

    def propagates(self, *ids):
        """
        遅延伝搬処理
        ids: 伝搬する対象の区間 
        """
        for i in reversed(ids):
            v = self.lazy[i]
            if v == ide_ele:
                continue
            lv = self.num.bit_length() - (2*i).bit_length() 
            upd = Ref[v[0]%10][lv]
            #print(v[0]%10, lv)
            self.lazy[2 * i] = [upd, pow_2_lv[lv]]
            self.lazy[2 * i + 1] = [upd, pow_2_lv[lv]]
            self.data[2 * i] = [upd, pow_2_lv[lv]]
            self.data[2 * i + 1] = [upd, pow_2_lv[lv]]
            self.lazy[i] = ide_ele

    def update(self, l, r, x):
        """
        区間[l, r)の値をxに更新
        l, r: index(0-index)
        x: update value
        """
        *ids, = self.gindex(l, r)
        #print(l, r, " ids = ", ids)
        self.propagates(*ids)
        l += self.num
        r += self.num
        while l < r:
            if l & 1:
                lv = self.num.bit_length() - l.bit_length() 
                #print(l, lv)
                upd = Ref[x[0]][lv]
                self.lazy[l] = [upd, pow_2_lv[lv]]
                self.data[l] = [upd, pow_2_lv[lv]]
                l += 1
            if r & 1:
                lv = self.num.bit_length() - (r-1).bit_length()
                upd = Ref[x[0]][lv]
                #print(r, lv)
                self.lazy[r - 1] = [upd, pow_2_lv[lv]]
                self.data[r - 1] = [upd, pow_2_lv[lv]]
            r >>= 1
            l >>= 1
        for i in ids:
            self.data[i] = self.segfunc(self.data[2 * i], self.data[2 * i + 1])


    def query(self, l, r):
        """
        [l, r)のsegfuncしたものを得る
        l: index(0-index)
        r: index(0-index)
        """
        *ids, = self.gindex(l, r)
        self.propagates(*ids)

        res = self.ide_ele

        l += self.num
        r += self.num
        while l < r:
            if l & 1:
                res = self.segfunc(self.data[l], res)
                l += 1
            if r & 1:
                res = self.segfunc(res, self.data[r - 1])
            l >>= 1
            r >>= 1
        return res
  
N, Q = map(int,input().split())
lst = LazySegmentTree([[1, 1] for _ in range(N)], segfunc, ide_ele)
#print(lst.data)
for _ in range(Q):
  L, R, D = map(int,input().split())
  L -= 1
  R -= 1
  lst.update(L, R+1, [D,1])
  print(lst.data[1][0])


Fixed submission:
class LazySegmentTree():
  def __init__(self, n, op, e, mapping, composition, id):
    self.n = n
    self.op = op
    self.e = e
    self.mapping = mapping
    self.composition = composition
    self.id = id
    self.log = (n - 1).bit_length()
    self.size = 1 << self.log
    self.data = [(e,e)] * (2 * self.size)
    self.lazy = [id] * (self.size)

  def update(self, k):
    self.data[k] = self.op(self.data[2 * k], self.data[2 * k + 1])

  def all_apply(self, k, f):
    self.data[k] = self.mapping(f, self.data[k])
    if k < self.size:
      self.lazy[k] = self.composition(f, self.lazy[k])

  def push(self, k):
    self.all_apply(2 * k, self.lazy[k])
    self.all_apply(2 * k + 1, self.lazy[k])
    self.lazy[k] = self.id

  def build(self, arr):
    #assert len(arr) == self.n
    for i, a in enumerate(arr):
      self.data[self.size + i] = a
    for i in range(self.size-1,0,-1):
      self.update(i)

  def set(self, p, x):
    #assert 0 <= p < self.n
    p += self.size
    for i in range(self.log,0,-1):
      self.push(p >> i)
    self.data[p] = x
    for i in range(1, self.log + 1):
      self.update(p >> i)

  def get(self, p):
    #assert 0 <= p < self.n
    p += self.size
    for i in range(1, self.log + 1):
      self.push(p >> i)
    return self.data[p]

  def prod(self, l, r):
    #assert 0 <= l <= r <= self.n
    if l == r: return self.e
    l += self.size
    r += self.size
    for i in range(1, self.log + 1)[::-1]:
      if ((l >> i) << i) != l: self.push(l >> i)
      if ((r >> i) << i) != r: self.push(r >> i)
    sml = smr = self.e
    while l < r:
      if l & 1:
        sml = self.op(sml, self.data[l])
        l += 1
      if r & 1:
        r -= 1
        smr = self.op(self.data[r], smr)
      l >>= 1
      r >>= 1
    return self.op(sml, smr)

  def apply(self, p, f):
    #assert 0 <= p < self.n
    p += self.size
    for i in range(1, self.log + 1)[::-1]:
      self.push(p >> i)
    self.data[p] = self.mapping(f, self.data[p])
    for i in range(1, self.log + 1):
      self.update(p >> i)

  def range_apply(self, l, r, f):
    #assert 0 <= l <= r <= self.n
    if l == r: return
    l += self.size
    r += self.size
    for i in range(1, self.log + 1)[::-1]:
      if ((l >> i) << i) != l: self.push(l >> i)
      if ((r >> i) << i) != r: self.push((r - 1) >> i)
    l2 = l
    r2 = r
    while l < r:
      if l & 1:
        self.all_apply(l, f)
        l += 1
      if r & 1:
        r -= 1
        self.all_apply(r, f)
      l >>= 1
      r >>= 1
    l = l2
    r = r2
    for i in range(1, self.log + 1):
      if ((l >> i) << i) != l: self.update(l >> i)
      if ((r >> i) << i) != r: self.update((r - 1) >> i)

  def max_right(self, l, g):
    #assert 0 <= l <= self.n
    #assert g(self.e)
    if l == self.n: return self.n
    l += self.size
    for i in range(1, self.log + 1)[::-1]:
      self.push(l >> i)
    sm = self.e
    while True:
      while l % 2 == 0: l >>= 1
      if not g(self.op(sm, self.data[l])):
        while l < self.size:
          self.push(l)
          l = 2 * l
          if g(self.op(sm, self.data[l])):
            sm = self.op(sm, self.data[l])
            l += 1
        return l - self.size
      sm = self.op(sm, self.data[l])
      l += 1
      if (l & -l) == l: return self.n

  def min_left(self, r, g):
    #assert 0 <= r <= self.n
    #assert g(self.e)
    if r == 0: return 0
    r += self.size
    for i in range(1, self.log + 1)[::-1]:
      self.push((r - 1) >> i)
    sm = self.e
    while True:
      r -= 1
      while r > 1 and r % 2: r >>= 1
      if not g(self.op(self.data[r], sm)):
        while r < self.size:
          self.push(r)
          r = 2 * r + 1
          if g(self.op(self.data[r], sm)):
            sm = self.op(self.data[r], sm)
            r -= 1
        return r + 1 - self.size
      sm = self.op(self.data[r], sm)
      if (r & -r) == r: return 0

import sys
input = sys.stdin.buffer.readline

INF = 10**18
MOD = 998244353

N, Q = map(int, input().split())

def op(x, y):
  xv, xr = x
  yv, yr = y
  return (xv + yv) % MOD, (xr + yr) % MOD

def mapping(p, x): #pが更新後の値, xが更新する前の値
  xv, xr = x
  if p != INF:
    return p * xr % MOD, xr
  return x

def composition(p, q):
  if p != INF:
    return p
  return q

lis = [[1,1] for _ in range(N)]
tmp = 1
for i in range(N-2,-1,-1):
  tmp *= 10 
  tmp %= MOD
  lis[i][0], lis[i][1] = tmp, tmp
#print(lis)
#arr = [(e,e) for e in lis]

e = 0
id = INF

lst = LazySegmentTree(N, op, e, mapping, composition, id)
lst.build(lis)

ans = [0]*Q

for i in range(Q):
  l, r, d = map(int, input().split())
  lst.range_apply(l - 1, r, d)
  ans[i] = lst.data[1][0]


print(*ans, sep='\n')

Buggy python submission (verdict: Runtime Error):
MOD = 998244353

Ref = [[0 for _ in range(100)] for _ in range(10)]
pow_2_lv = [pow(2,lv) for lv in range(30)]
pow_10_MOD = [pow(10, i, MOD) for i in range(50)]

for i in range(1,10):
  Ref[i][0] = i
  for s in range(1,30):
    Ref[i][s] += (Ref[i][s-1] * (pow(10, pow_2_lv[s-1], MOD) + 1)) % MOD
    Ref[i][s] %= MOD
#print(Ref)  
#####segfunc#####
def segfunc(x, y):
    return [(x[0] * pow_10_MOD[y[1]] + y[0]) % MOD, x[1] + y[1]]
#################

#####ide_ele#####
ide_ele = [0,0]
#################

class LazySegmentTree:
    """
    init(init_val, ide_ele): 配列init_valで初期化 O(N)
    update(l, r, x): 区間[l, r)をxに更新 O(logN)
    query(l, r): 区間[l, r)をsegfuncしたものを返す O(logN)
    """
    def __init__(self, init_val, segfunc, ide_ele):
        """
        init_val: 配列の初期値
        segfunc: 区間にしたい操作
        ide_ele: 単位元
        num: n以上の最小の2のべき乗
        data: 値配列(1-index)
        lazy: 遅延配列(1-index)
        """
        n = len(init_val)
        self.segfunc = segfunc
        self.ide_ele = ide_ele
        self.num = 1 << (n - 1).bit_length()
        self.data = [ide_ele] * 2 * self.num
        self.lazy = [ide_ele] * 2 * self.num
        # 配列の値を葉にセット
        for i in range(n):
            self.data[self.num + i] = init_val[i]
        # 構築していく
        for i in range(self.num - 1, 0, -1):
            self.data[i] = self.segfunc(self.data[2 * i], self.data[2 * i + 1])

    def gindex(self, l, r):
            """
            伝搬する対象の区間を求める
            lm: 伝搬する必要のある最大の左閉区間
            rm: 伝搬する必要のある最大の右開区間
            """
            l += self.num
            r += self.num
            lm = l >> (l & -l).bit_length()
            rm = r >> (r & -r).bit_length()

            while r > l:
                if l <= lm:
                    yield l
                if r <= rm:
                    yield r
                r >>= 1
                l >>= 1
            while l:
                yield l
                l >>= 1

    def propagates(self, *ids):
        """
        遅延伝搬処理
        ids: 伝搬する対象の区間 
        """
        for i in reversed(ids):
            v = self.lazy[i]
            if v == ide_ele:
                continue
            lv = self.num.bit_length() - (2*i).bit_length() 
            upd = Ref[v[0]%10][lv]
            #print(v[0]%10, lv)
            self.lazy[2 * i] = [upd, pow_2_lv[lv]]
            self.lazy[2 * i + 1] = [upd, pow_2_lv[lv]]
            self.data[2 * i] = [upd, pow_2_lv[lv]]
            self.data[2 * i + 1] = [upd, pow_2_lv[lv]]
            self.lazy[i] = ide_ele

    def update(self, l, r, x):
        """
        区間[l, r)の値をxに更新
        l, r: index(0-index)
        x: update value
        """
        *ids, = self.gindex(l, r)
        #print(l, r, " ids = ", ids)
        self.propagates(*ids)
        l += self.num
        r += self.num
        while l < r:
            if l & 1:
                lv = self.num.bit_length() - l.bit_length() 
                #print(l, lv)
                upd = Ref[x[0]][lv]
                self.lazy[l] = [upd, pow_2_lv[lv]]
                self.data[l] = [upd, pow_2_lv[lv]]
                l += 1
            if r & 1:
                lv = self.num.bit_length() - (r-1).bit_length()
                upd = Ref[x[0]][lv]
                #print(r, lv)
                self.lazy[r - 1] = [upd, pow_2_lv[lv]]
                self.data[r - 1] = [upd, pow_2_lv[lv]]
            r >>= 1
            l >>= 1
        for i in ids:
            self.data[i] = self.segfunc(self.data[2 * i], self.data[2 * i + 1])


    def query(self, l, r):
        """
        [l, r)のsegfuncしたものを得る
        l: index(0-index)
        r: index(0-index)
        """
        *ids, = self.gindex(l, r)
        self.propagates(*ids)

        res = self.ide_ele

        l += self.num
        r += self.num
        while l < r:
            if l & 1:
                res = self.segfunc(self.data[l], res)
                l += 1
            if r & 1:
                res = self.segfunc(res, self.data[r - 1])
            l >>= 1
            r >>= 1
        return res
  
N, Q = map(int,input().split())
lst = LazySegmentTree([[1, 1] for _ in range(N)], segfunc, ide_ele)
#print(lst.data)
for _ in range(Q):
  L, R, D = map(int,input().split())
  L -= 1
  R -= 1
  lst.update(L, R+1, [D,1])
  print(lst.data[1][0])


Fixed submission:
class LazySegmentTree():
  def __init__(self, n, op, e, mapping, composition, id):
    self.n = n
    self.op = op
    self.e = e
    self.mapping = mapping
    self.composition = composition
    self.id = id
    self.log = (n - 1).bit_length()
    self.size = 1 << self.log
    self.data = [(e,e)] * (2 * self.size)
    self.lazy = [id] * (self.size)

  def update(self, k):
    self.data[k] = self.op(self.data[2 * k], self.data[2 * k + 1])

  def all_apply(self, k, f):
    self.data[k] = self.mapping(f, self.data[k])
    if k < self.size:
      self.lazy[k] = self.composition(f, self.lazy[k])

  def push(self, k):
    self.all_apply(2 * k, self.lazy[k])
    self.all_apply(2 * k + 1, self.lazy[k])
    self.lazy[k] = self.id

  def build(self, arr):
    #assert len(arr) == self.n
    for i, a in enumerate(arr):
      self.data[self.size + i] = a
    for i in range(self.size-1,0,-1):
      self.update(i)

  def set(self, p, x):
    #assert 0 <= p < self.n
    p += self.size
    for i in range(self.log,0,-1):
      self.push(p >> i)
    self.data[p] = x
    for i in range(1, self.log + 1):
      self.update(p >> i)

  def get(self, p):
    #assert 0 <= p < self.n
    p += self.size
    for i in range(1, self.log + 1):
      self.push(p >> i)
    return self.data[p]

  def prod(self, l, r):
    #assert 0 <= l <= r <= self.n
    if l == r: return self.e
    l += self.size
    r += self.size
    for i in range(1, self.log + 1)[::-1]:
      if ((l >> i) << i) != l: self.push(l >> i)
      if ((r >> i) << i) != r: self.push(r >> i)
    sml = smr = self.e
    while l < r:
      if l & 1:
        sml = self.op(sml, self.data[l])
        l += 1
      if r & 1:
        r -= 1
        smr = self.op(self.data[r], smr)
      l >>= 1
      r >>= 1
    return self.op(sml, smr)

  def apply(self, p, f):
    #assert 0 <= p < self.n
    p += self.size
    for i in range(1, self.log + 1)[::-1]:
      self.push(p >> i)
    self.data[p] = self.mapping(f, self.data[p])
    for i in range(1, self.log + 1):
      self.update(p >> i)

  def range_apply(self, l, r, f):
    #assert 0 <= l <= r <= self.n
    if l == r: return
    l += self.size
    r += self.size
    for i in range(1, self.log + 1)[::-1]:
      if ((l >> i) << i) != l: self.push(l >> i)
      if ((r >> i) << i) != r: self.push((r - 1) >> i)
    l2 = l
    r2 = r
    while l < r:
      if l & 1:
        self.all_apply(l, f)
        l += 1
      if r & 1:
        r -= 1
        self.all_apply(r, f)
      l >>= 1
      r >>= 1
    l = l2
    r = r2
    for i in range(1, self.log + 1):
      if ((l >> i) << i) != l: self.update(l >> i)
      if ((r >> i) << i) != r: self.update((r - 1) >> i)

  def max_right(self, l, g):
    #assert 0 <= l <= self.n
    #assert g(self.e)
    if l == self.n: return self.n
    l += self.size
    for i in range(1, self.log + 1)[::-1]:
      self.push(l >> i)
    sm = self.e
    while True:
      while l % 2 == 0: l >>= 1
      if not g(self.op(sm, self.data[l])):
        while l < self.size:
          self.push(l)
          l = 2 * l
          if g(self.op(sm, self.data[l])):
            sm = self.op(sm, self.data[l])
            l += 1
        return l - self.size
      sm = self.op(sm, self.data[l])
      l += 1
      if (l & -l) == l: return self.n

  def min_left(self, r, g):
    #assert 0 <= r <= self.n
    #assert g(self.e)
    if r == 0: return 0
    r += self.size
    for i in range(1, self.log + 1)[::-1]:
      self.push((r - 1) >> i)
    sm = self.e
    while True:
      r -= 1
      while r > 1 and r % 2: r >>= 1
      if not g(self.op(self.data[r], sm)):
        while r < self.size:
          self.push(r)
          r = 2 * r + 1
          if g(self.op(self.data[r], sm)):
            sm = self.op(self.data[r], sm)
            r -= 1
        return r + 1 - self.size
      sm = self.op(self.data[r], sm)
      if (r & -r) == r: return 0

import sys
input = sys.stdin.buffer.readline

INF = 10**18
MOD = 998244353

N, Q = map(int, input().split())

def op(x, y):
  xv, xr = x
  yv, yr = y
  return (xv + yv) % MOD, (xr + yr) % MOD

def mapping(p, x): #pが更新後の値, xが更新する前の値
  xv, xr = x
  if p != INF:
    return p * xr % MOD, xr
  return x

def composition(p, q):
  if p != INF:
    return p
  return q

lis = [[1,1] for _ in range(N)]
tmp = 1
for i in range(N-2,-1,-1):
  tmp *= 10 
  tmp %= MOD
  lis[i][0], lis[i][1] = tmp, tmp
#print(lis)
#arr = [(e,e) for e in lis]

e = 0
id = INF

lst = LazySegmentTree(N, op, e, mapping, composition, id)
lst.build(lis)

ans = [0]*Q

for i in range(Q):
  l, r, d = map(int, input().split())
  lst.range_apply(l - 1, r, d)
  ans[i] = lst.data[1][0]


print(*ans, sep='\n')

Buggy python submission (verdict: Time Limit Exceeded):
MOD = 998244353

Ref = [[0 for _ in range(100)] for _ in range(10)]
pow_2_lv = [pow(2,lv) for lv in range(30)]
for i in range(1,10):
  Ref[i][0] = i
  for s in range(1,30):
    Ref[i][s] += Ref[i][s-1] * (pow(10, pow(2, s-1), MOD) + 1) % MOD
#print(Ref)  
#####segfunc#####
def segfunc(x, y):
    return [(x[0] * pow(10, y[1], MOD) + y[0]) % MOD, x[1] + y[1]]
#################

#####ide_ele#####
ide_ele = [0,0]
#################

class LazySegmentTree:
    """
    init(init_val, ide_ele): 配列init_valで初期化 O(N)
    update(l, r, x): 区間[l, r)をxに更新 O(logN)
    query(l, r): 区間[l, r)をsegfuncしたものを返す O(logN)
    """
    def __init__(self, init_val, segfunc, ide_ele):
        """
        init_val: 配列の初期値
        segfunc: 区間にしたい操作
        ide_ele: 単位元
        num: n以上の最小の2のべき乗
        data: 値配列(1-index)
        lazy: 遅延配列(1-index)
        """
        n = len(init_val)
        self.segfunc = segfunc
        self.ide_ele = ide_ele
        self.num = 1 << (n - 1).bit_length()
        self.data = [ide_ele] * 2 * self.num
        self.lazy = [ide_ele] * 2 * self.num
        # 配列の値を葉にセット
        for i in range(n):
            self.data[self.num + i] = init_val[i]
        # 構築していく
        for i in range(self.num - 1, 0, -1):
            self.data[i] = self.segfunc(self.data[2 * i], self.data[2 * i + 1])

    def gindex(self, l, r):
            """
            伝搬する対象の区間を求める
            lm: 伝搬する必要のある最大の左閉区間
            rm: 伝搬する必要のある最大の右開区間
            """
            l += self.num
            r += self.num
            lm = l >> (l & -l).bit_length()
            rm = r >> (r & -r).bit_length()

            while r > l:
                if l <= lm:
                    yield l
                if r <= rm:
                    yield r
                r >>= 1
                l >>= 1
            while l:
                yield l
                l >>= 1

    def propagates(self, *ids):
        """
        遅延伝搬処理
        ids: 伝搬する対象の区間 
        """
        for i in reversed(ids):
            v = self.lazy[i]
            if v == ide_ele:
                continue
            lv = self.num.bit_length() - (2*i).bit_length() 
            upd = Ref[v[0]%10][lv]
            #print(v[0]%10, lv)
            self.lazy[2 * i] = [upd, pow_2_lv[lv]]
            self.lazy[2 * i + 1] = [upd, pow_2_lv[lv]]
            self.data[2 * i] = [upd, pow_2_lv[lv]]
            self.data[2 * i + 1] = [upd, pow_2_lv[lv]]
            self.lazy[i] = ide_ele

    def update(self, l, r, x):
        """
        区間[l, r)の値をxに更新
        l, r: index(0-index)
        x: update value
        """
        *ids, = self.gindex(l, r)
        #print(l, r, " ids = ", ids)
        self.propagates(*ids)
        l += self.num
        r += self.num
        while l < r:
            if l & 1:
                lv = self.num.bit_length() - l.bit_length() 
                #print(l, lv)
                upd = Ref[x[0]][lv]
                self.lazy[l] = [upd, pow_2_lv[lv]]
                self.data[l] = [upd, pow_2_lv[lv]]
                l += 1
            if r & 1:
                lv = self.num.bit_length() - (r-1).bit_length()
                upd = Ref[x[0]][lv]
                #print(r, lv)
                self.lazy[r - 1] = [upd, pow_2_lv[lv]]
                self.data[r - 1] = [upd, pow_2_lv[lv]]
            r >>= 1
            l >>= 1
        for i in ids:
            self.data[i] = self.segfunc(self.data[2 * i], self.data[2 * i + 1])


    def query(self, l, r):
        """
        [l, r)のsegfuncしたものを得る
        l: index(0-index)
        r: index(0-index)
        """
        *ids, = self.gindex(l, r)
        self.propagates(*ids)

        res = self.ide_ele

        l += self.num
        r += self.num
        while l < r:
            if l & 1:
                res = self.segfunc(self.data[l], res)
                l += 1
            if r & 1:
                res = self.segfunc(res, self.data[r - 1])
            l >>= 1
            r >>= 1
        return res
  
N, Q = map(int,input().split())
lst = LazySegmentTree([[1, 1] for _ in range(N)], segfunc, ide_ele)
#print(lst.data)
for _ in range(Q):
  L, R, D = map(int,input().split())
  L -= 1
  R -= 1
  lst.update(L, R+1, [D,1])
  print(lst.data[1][0])

Fixed submission:
class LazySegmentTree():
  def __init__(self, n, op, e, mapping, composition, id):
    self.n = n
    self.op = op
    self.e = e
    self.mapping = mapping
    self.composition = composition
    self.id = id
    self.log = (n - 1).bit_length()
    self.size = 1 << self.log
    self.data = [(e,e)] * (2 * self.size)
    self.lazy = [id] * (self.size)

  def update(self, k):
    self.data[k] = self.op(self.data[2 * k], self.data[2 * k + 1])

  def all_apply(self, k, f):
    self.data[k] = self.mapping(f, self.data[k])
    if k < self.size:
      self.lazy[k] = self.composition(f, self.lazy[k])

  def push(self, k):
    self.all_apply(2 * k, self.lazy[k])
    self.all_apply(2 * k + 1, self.lazy[k])
    self.lazy[k] = self.id

  def build(self, arr):
    #assert len(arr) == self.n
    for i, a in enumerate(arr):
      self.data[self.size + i] = a
    for i in range(self.size-1,0,-1):
      self.update(i)

  def set(self, p, x):
    #assert 0 <= p < self.n
    p += self.size
    for i in range(self.log,0,-1):
      self.push(p >> i)
    self.data[p] = x
    for i in range(1, self.log + 1):
      self.update(p >> i)

  def get(self, p):
    #assert 0 <= p < self.n
    p += self.size
    for i in range(1, self.log + 1):
      self.push(p >> i)
    return self.data[p]

  def prod(self, l, r):
    #assert 0 <= l <= r <= self.n
    if l == r: return self.e
    l += self.size
    r += self.size
    for i in range(1, self.log + 1)[::-1]:
      if ((l >> i) << i) != l: self.push(l >> i)
      if ((r >> i) << i) != r: self.push(r >> i)
    sml = smr = self.e
    while l < r:
      if l & 1:
        sml = self.op(sml, self.data[l])
        l += 1
      if r & 1:
        r -= 1
        smr = self.op(self.data[r], smr)
      l >>= 1
      r >>= 1
    return self.op(sml, smr)

  def apply(self, p, f):
    #assert 0 <= p < self.n
    p += self.size
    for i in range(1, self.log + 1)[::-1]:
      self.push(p >> i)
    self.data[p] = self.mapping(f, self.data[p])
    for i in range(1, self.log + 1):
      self.update(p >> i)

  def range_apply(self, l, r, f):
    #assert 0 <= l <= r <= self.n
    if l == r: return
    l += self.size
    r += self.size
    for i in range(1, self.log + 1)[::-1]:
      if ((l >> i) << i) != l: self.push(l >> i)
      if ((r >> i) << i) != r: self.push((r - 1) >> i)
    l2 = l
    r2 = r
    while l < r:
      if l & 1:
        self.all_apply(l, f)
        l += 1
      if r & 1:
        r -= 1
        self.all_apply(r, f)
      l >>= 1
      r >>= 1
    l = l2
    r = r2
    for i in range(1, self.log + 1):
      if ((l >> i) << i) != l: self.update(l >> i)
      if ((r >> i) << i) != r: self.update((r - 1) >> i)

  def max_right(self, l, g):
    #assert 0 <= l <= self.n
    #assert g(self.e)
    if l == self.n: return self.n
    l += self.size
    for i in range(1, self.log + 1)[::-1]:
      self.push(l >> i)
    sm = self.e
    while True:
      while l % 2 == 0: l >>= 1
      if not g(self.op(sm, self.data[l])):
        while l < self.size:
          self.push(l)
          l = 2 * l
          if g(self.op(sm, self.data[l])):
            sm = self.op(sm, self.data[l])
            l += 1
        return l - self.size
      sm = self.op(sm, self.data[l])
      l += 1
      if (l & -l) == l: return self.n

  def min_left(self, r, g):
    #assert 0 <= r <= self.n
    #assert g(self.e)
    if r == 0: return 0
    r += self.size
    for i in range(1, self.log + 1)[::-1]:
      self.push((r - 1) >> i)
    sm = self.e
    while True:
      r -= 1
      while r > 1 and r % 2: r >>= 1
      if not g(self.op(self.data[r], sm)):
        while r < self.size:
          self.push(r)
          r = 2 * r + 1
          if g(self.op(self.data[r], sm)):
            sm = self.op(self.data[r], sm)
            r -= 1
        return r + 1 - self.size
      sm = self.op(self.data[r], sm)
      if (r & -r) == r: return 0

import sys
input = sys.stdin.buffer.readline

INF = 10**18
MOD = 998244353

N, Q = map(int, input().split())

def op(x, y):
  xv, xr = x
  yv, yr = y
  return (xv + yv) % MOD, (xr + yr) % MOD

def mapping(p, x): #pが更新後の値, xが更新する前の値
  xv, xr = x
  if p != INF:
    return p * xr % MOD, xr
  return x

def composition(p, q):
  if p != INF:
    return p
  return q

lis = [[1,1] for _ in range(N)]
tmp = 1
for i in range(N-2,-1,-1):
  tmp *= 10 
  tmp %= MOD
  lis[i][0], lis[i][1] = tmp, tmp
#print(lis)
#arr = [(e,e) for e in lis]

e = 0
id = INF

lst = LazySegmentTree(N, op, e, mapping, composition, id)
lst.build(lis)

ans = [0]*Q

for i in range(Q):
  l, r, d = map(int, input().split())
  lst.range_apply(l - 1, r, d)
  ans[i] = lst.data[1][0]


print(*ans, sep='\n')

Buggy python submission (verdict: Time Limit Exceeded):
MOD = 998244353

Ref = [[0 for _ in range(100)] for _ in range(10)]
pow_2_lv = [pow(2,lv) for lv in range(30)]
for i in range(1,10):
  Ref[i][0] = i
  for s in range(1,30):
    Ref[i][s] += Ref[i][s-1] * (pow(10, pow(2, s-1), MOD) + 1) % MOD
#print(Ref)  
#####segfunc#####
def segfunc(x, y):
    return [(x[0] * pow(10, y[1], MOD) + y[0]) % MOD, x[1] + y[1]]
#################

#####ide_ele#####
ide_ele = [0,0]
#################

class LazySegmentTree:
    """
    init(init_val, ide_ele): 配列init_valで初期化 O(N)
    update(l, r, x): 区間[l, r)をxに更新 O(logN)
    query(l, r): 区間[l, r)をsegfuncしたものを返す O(logN)
    """
    def __init__(self, init_val, segfunc, ide_ele):
        """
        init_val: 配列の初期値
        segfunc: 区間にしたい操作
        ide_ele: 単位元
        num: n以上の最小の2のべき乗
        data: 値配列(1-index)
        lazy: 遅延配列(1-index)
        """
        n = len(init_val)
        self.segfunc = segfunc
        self.ide_ele = ide_ele
        self.num = 1 << (n - 1).bit_length()
        self.data = [ide_ele] * 2 * self.num
        self.lazy = [ide_ele] * 2 * self.num
        # 配列の値を葉にセット
        for i in range(n):
            self.data[self.num + i] = init_val[i]
        # 構築していく
        for i in range(self.num - 1, 0, -1):
            self.data[i] = self.segfunc(self.data[2 * i], self.data[2 * i + 1])

    def gindex(self, l, r):
            """
            伝搬する対象の区間を求める
            lm: 伝搬する必要のある最大の左閉区間
            rm: 伝搬する必要のある最大の右開区間
            """
            l += self.num
            r += self.num
            lm = l >> (l & -l).bit_length()
            rm = r >> (r & -r).bit_length()

            while r > l:
                if l <= lm:
                    yield l
                if r <= rm:
                    yield r
                r >>= 1
                l >>= 1
            while l:
                yield l
                l >>= 1

    def propagates(self, *ids):
        """
        遅延伝搬処理
        ids: 伝搬する対象の区間 
        """
        for i in reversed(ids):
            v = self.lazy[i]
            if v == ide_ele:
                continue
            lv = self.num.bit_length() - (2*i).bit_length() 
            #print(v[0]%10, lv)
            self.lazy[2 * i] = [Ref[v[0]%10][lv], pow_2_lv[lv]]
            self.lazy[2 * i + 1] = [Ref[v[0]%10][lv], pow_2_lv[lv]]
            self.data[2 * i] = [Ref[v[0]%10][lv], pow_2_lv[lv]]
            self.data[2 * i + 1] = [Ref[v[0]%10][lv], pow_2_lv[lv]]
            self.lazy[i] = ide_ele

    def update(self, l, r, x):
        """
        区間[l, r)の値をxに更新
        l, r: index(0-index)
        x: update value
        """
        *ids, = self.gindex(l, r)
        #print(l, r, " ids = ", ids)
        self.propagates(*ids)
        l += self.num
        r += self.num
        while l < r:
            if l & 1:
                lv = self.num.bit_length() - l.bit_length() 
                #print(l, lv)
                self.lazy[l] = [Ref[x[0]][lv], pow_2_lv[lv]]
                self.data[l] = [Ref[x[0]][lv], pow_2_lv[lv]]
                l += 1
            if r & 1:
                lv = self.num.bit_length() - (r-1).bit_length()
                #print(r, lv)
                self.lazy[r - 1] = [Ref[x[0]][lv], pow_2_lv[lv]]
                self.data[r - 1] = [Ref[x[0]][lv], pow_2_lv[lv]]
            r >>= 1
            l >>= 1
        for i in ids:
            self.data[i] = self.segfunc(self.data[2 * i], self.data[2 * i + 1])


    def query(self, l, r):
        """
        [l, r)のsegfuncしたものを得る
        l: index(0-index)
        r: index(0-index)
        """
        *ids, = self.gindex(l, r)
        self.propagates(*ids)

        res = self.ide_ele

        l += self.num
        r += self.num
        while l < r:
            if l & 1:
                res = self.segfunc(self.data[l], res)
                l += 1
            if r & 1:
                res = self.segfunc(res, self.data[r - 1])
            l >>= 1
            r >>= 1
        return res
  
N, Q = map(int,input().split())
lst = LazySegmentTree([[1, 1] for _ in range(N)], segfunc, ide_ele)
#print(lst.data)
for _ in range(Q):
  L, R, D = map(int,input().split())
  L -= 1
  R -= 1
  lst.update(L, R+1, [D,1])
  print(lst.data[1][0])

Fixed submission:
class LazySegmentTree():
  def __init__(self, n, op, e, mapping, composition, id):
    self.n = n
    self.op = op
    self.e = e
    self.mapping = mapping
    self.composition = composition
    self.id = id
    self.log = (n - 1).bit_length()
    self.size = 1 << self.log
    self.data = [(e,e)] * (2 * self.size)
    self.lazy = [id] * (self.size)

  def update(self, k):
    self.data[k] = self.op(self.data[2 * k], self.data[2 * k + 1])

  def all_apply(self, k, f):
    self.data[k] = self.mapping(f, self.data[k])
    if k < self.size:
      self.lazy[k] = self.composition(f, self.lazy[k])

  def push(self, k):
    self.all_apply(2 * k, self.lazy[k])
    self.all_apply(2 * k + 1, self.lazy[k])
    self.lazy[k] = self.id

  def build(self, arr):
    #assert len(arr) == self.n
    for i, a in enumerate(arr):
      self.data[self.size + i] = a
    for i in range(self.size-1,0,-1):
      self.update(i)

  def set(self, p, x):
    #assert 0 <= p < self.n
    p += self.size
    for i in range(self.log,0,-1):
      self.push(p >> i)
    self.data[p] = x
    for i in range(1, self.log + 1):
      self.update(p >> i)

  def get(self, p):
    #assert 0 <= p < self.n
    p += self.size
    for i in range(1, self.log + 1):
      self.push(p >> i)
    return self.data[p]

  def prod(self, l, r):
    #assert 0 <= l <= r <= self.n
    if l == r: return self.e
    l += self.size
    r += self.size
    for i in range(1, self.log + 1)[::-1]:
      if ((l >> i) << i) != l: self.push(l >> i)
      if ((r >> i) << i) != r: self.push(r >> i)
    sml = smr = self.e
    while l < r:
      if l & 1:
        sml = self.op(sml, self.data[l])
        l += 1
      if r & 1:
        r -= 1
        smr = self.op(self.data[r], smr)
      l >>= 1
      r >>= 1
    return self.op(sml, smr)

  def apply(self, p, f):
    #assert 0 <= p < self.n
    p += self.size
    for i in range(1, self.log + 1)[::-1]:
      self.push(p >> i)
    self.data[p] = self.mapping(f, self.data[p])
    for i in range(1, self.log + 1):
      self.update(p >> i)

  def range_apply(self, l, r, f):
    #assert 0 <= l <= r <= self.n
    if l == r: return
    l += self.size
    r += self.size
    for i in range(1, self.log + 1)[::-1]:
      if ((l >> i) << i) != l: self.push(l >> i)
      if ((r >> i) << i) != r: self.push((r - 1) >> i)
    l2 = l
    r2 = r
    while l < r:
      if l & 1:
        self.all_apply(l, f)
        l += 1
      if r & 1:
        r -= 1
        self.all_apply(r, f)
      l >>= 1
      r >>= 1
    l = l2
    r = r2
    for i in range(1, self.log + 1):
      if ((l >> i) << i) != l: self.update(l >> i)
      if ((r >> i) << i) != r: self.update((r - 1) >> i)

  def max_right(self, l, g):
    #assert 0 <= l <= self.n
    #assert g(self.e)
    if l == self.n: return self.n
    l += self.size
    for i in range(1, self.log + 1)[::-1]:
      self.push(l >> i)
    sm = self.e
    while True:
      while l % 2 == 0: l >>= 1
      if not g(self.op(sm, self.data[l])):
        while l < self.size:
          self.push(l)
          l = 2 * l
          if g(self.op(sm, self.data[l])):
            sm = self.op(sm, self.data[l])
            l += 1
        return l - self.size
      sm = self.op(sm, self.data[l])
      l += 1
      if (l & -l) == l: return self.n

  def min_left(self, r, g):
    #assert 0 <= r <= self.n
    #assert g(self.e)
    if r == 0: return 0
    r += self.size
    for i in range(1, self.log + 1)[::-1]:
      self.push((r - 1) >> i)
    sm = self.e
    while True:
      r -= 1
      while r > 1 and r % 2: r >>= 1
      if not g(self.op(self.data[r], sm)):
        while r < self.size:
          self.push(r)
          r = 2 * r + 1
          if g(self.op(self.data[r], sm)):
            sm = self.op(self.data[r], sm)
            r -= 1
        return r + 1 - self.size
      sm = self.op(self.data[r], sm)
      if (r & -r) == r: return 0

import sys
input = sys.stdin.buffer.readline

INF = 10**18
MOD = 998244353

N, Q = map(int, input().split())

def op(x, y):
  xv, xr = x
  yv, yr = y
  return (xv + yv) % MOD, (xr + yr) % MOD

def mapping(p, x): #pが更新後の値, xが更新する前の値
  xv, xr = x
  if p != INF:
    return p * xr % MOD, xr
  return x

def composition(p, q):
  if p != INF:
    return p
  return q

lis = [[1,1] for _ in range(N)]
tmp = 1
for i in range(N-2,-1,-1):
  tmp *= 10 
  tmp %= MOD
  lis[i][0], lis[i][1] = tmp, tmp
#print(lis)
#arr = [(e,e) for e in lis]

e = 0
id = INF

lst = LazySegmentTree(N, op, e, mapping, composition, id)
lst.build(lis)

ans = [0]*Q

for i in range(Q):
  l, r, d = map(int, input().split())
  lst.range_apply(l - 1, r, d)
  ans[i] = lst.data[1][0]


print(*ans, sep='\n')

Buggy python submission (verdict: Time Limit Exceeded):
MOD = 998244353

Ref = [[0 for _ in range(100)] for _ in range(10)]
for i in range(1,10):
  Ref[i][0] = i
  for s in range(1,30):
    Ref[i][s] += Ref[i][s-1] * (pow(10, pow(2, s-1), MOD) + 1) % MOD
#print(Ref)  
#####segfunc#####
def segfunc(x, y):
    return [(x[0] * pow(10, y[1], MOD) + y[0]) % MOD, x[1] + y[1]]
#################

#####ide_ele#####
ide_ele = [0,0]
#################

class LazySegmentTree:
    """
    init(init_val, ide_ele): 配列init_valで初期化 O(N)
    update(l, r, x): 区間[l, r)をxに更新 O(logN)
    query(l, r): 区間[l, r)をsegfuncしたものを返す O(logN)
    """
    def __init__(self, init_val, segfunc, ide_ele):
        """
        init_val: 配列の初期値
        segfunc: 区間にしたい操作
        ide_ele: 単位元
        num: n以上の最小の2のべき乗
        data: 値配列(1-index)
        lazy: 遅延配列(1-index)
        """
        n = len(init_val)
        self.segfunc = segfunc
        self.ide_ele = ide_ele
        self.num = 1 << (n - 1).bit_length()
        self.data = [ide_ele] * 2 * self.num
        self.lazy = [ide_ele] * 2 * self.num
        # 配列の値を葉にセット
        for i in range(n):
            self.data[self.num + i] = init_val[i]
        # 構築していく
        for i in range(self.num - 1, 0, -1):
            self.data[i] = self.segfunc(self.data[2 * i], self.data[2 * i + 1])

    def gindex(self, l, r):
            """
            伝搬する対象の区間を求める
            lm: 伝搬する必要のある最大の左閉区間
            rm: 伝搬する必要のある最大の右開区間
            """
            l += self.num
            r += self.num
            lm = l >> (l & -l).bit_length()
            rm = r >> (r & -r).bit_length()

            while r > l:
                if l <= lm:
                    yield l
                if r <= rm:
                    yield r
                r >>= 1
                l >>= 1
            while l:
                yield l
                l >>= 1

    def propagates(self, *ids):
        """
        遅延伝搬処理
        ids: 伝搬する対象の区間 
        """
        for i in reversed(ids):
            v = self.lazy[i]
            if v == ide_ele:
                continue
            lv = self.num.bit_length() - (2*i).bit_length() 
            #print(v[0]%10, lv)
            self.lazy[2 * i] = [Ref[v[0]%10][lv], pow(2,lv)]
            self.lazy[2 * i + 1] = [Ref[v[0]%10][lv], pow(2,lv)]
            self.data[2 * i] = [Ref[v[0]%10][lv], pow(2,lv)]
            self.data[2 * i + 1] = [Ref[v[0]%10][lv], pow(2,lv)]
            self.lazy[i] = ide_ele

    def update(self, l, r, x):
        """
        区間[l, r)の値をxに更新
        l, r: index(0-index)
        x: update value
        """
        *ids, = self.gindex(l, r)
        #print(l, r, " ids = ", ids)
        self.propagates(*ids)
        l += self.num
        r += self.num
        while l < r:
            if l & 1:
                lv = self.num.bit_length() - l.bit_length() 
                #print(l, lv)
                self.lazy[l] = [Ref[x[0]][lv], pow(2,lv)]
                self.data[l] = [Ref[x[0]][lv], pow(2,lv)]
                l += 1
            if r & 1:
                lv = self.num.bit_length() - (r-1).bit_length()
                #print(r, lv)
                self.lazy[r - 1] = [Ref[x[0]][lv], pow(2,lv)]
                self.data[r - 1] = [Ref[x[0]][lv], pow(2,lv)]
            r >>= 1
            l >>= 1
        for i in ids:
            self.data[i] = self.segfunc(self.data[2 * i], self.data[2 * i + 1])


    def query(self, l, r):
        """
        [l, r)のsegfuncしたものを得る
        l: index(0-index)
        r: index(0-index)
        """
        *ids, = self.gindex(l, r)
        self.propagates(*ids)

        res = self.ide_ele

        l += self.num
        r += self.num
        while l < r:
            if l & 1:
                res = self.segfunc(self.data[l], res)
                l += 1
            if r & 1:
                res = self.segfunc(res, self.data[r - 1])
            l >>= 1
            r >>= 1
        return res
  
N, Q = map(int,input().split())
lst = LazySegmentTree([[1, 1] for _ in range(N)], segfunc, ide_ele)
#print(lst.data)
for _ in range(Q):
  L, R, D = map(int,input().split())
  L -= 1
  R -= 1
  lst.update(L, R+1, [D,1])
  print(lst.data[1][0])

Fixed submission:
class LazySegmentTree():
  def __init__(self, n, op, e, mapping, composition, id):
    self.n = n
    self.op = op
    self.e = e
    self.mapping = mapping
    self.composition = composition
    self.id = id
    self.log = (n - 1).bit_length()
    self.size = 1 << self.log
    self.data = [(e,e)] * (2 * self.size)
    self.lazy = [id] * (self.size)

  def update(self, k):
    self.data[k] = self.op(self.data[2 * k], self.data[2 * k + 1])

  def all_apply(self, k, f):
    self.data[k] = self.mapping(f, self.data[k])
    if k < self.size:
      self.lazy[k] = self.composition(f, self.lazy[k])

  def push(self, k):
    self.all_apply(2 * k, self.lazy[k])
    self.all_apply(2 * k + 1, self.lazy[k])
    self.lazy[k] = self.id

  def build(self, arr):
    #assert len(arr) == self.n
    for i, a in enumerate(arr):
      self.data[self.size + i] = a
    for i in range(self.size-1,0,-1):
      self.update(i)

  def set(self, p, x):
    #assert 0 <= p < self.n
    p += self.size
    for i in range(self.log,0,-1):
      self.push(p >> i)
    self.data[p] = x
    for i in range(1, self.log + 1):
      self.update(p >> i)

  def get(self, p):
    #assert 0 <= p < self.n
    p += self.size
    for i in range(1, self.log + 1):
      self.push(p >> i)
    return self.data[p]

  def prod(self, l, r):
    #assert 0 <= l <= r <= self.n
    if l == r: return self.e
    l += self.size
    r += self.size
    for i in range(1, self.log + 1)[::-1]:
      if ((l >> i) << i) != l: self.push(l >> i)
      if ((r >> i) << i) != r: self.push(r >> i)
    sml = smr = self.e
    while l < r:
      if l & 1:
        sml = self.op(sml, self.data[l])
        l += 1
      if r & 1:
        r -= 1
        smr = self.op(self.data[r], smr)
      l >>= 1
      r >>= 1
    return self.op(sml, smr)

  def apply(self, p, f):
    #assert 0 <= p < self.n
    p += self.size
    for i in range(1, self.log + 1)[::-1]:
      self.push(p >> i)
    self.data[p] = self.mapping(f, self.data[p])
    for i in range(1, self.log + 1):
      self.update(p >> i)

  def range_apply(self, l, r, f):
    #assert 0 <= l <= r <= self.n
    if l == r: return
    l += self.size
    r += self.size
    for i in range(1, self.log + 1)[::-1]:
      if ((l >> i) << i) != l: self.push(l >> i)
      if ((r >> i) << i) != r: self.push((r - 1) >> i)
    l2 = l
    r2 = r
    while l < r:
      if l & 1:
        self.all_apply(l, f)
        l += 1
      if r & 1:
        r -= 1
        self.all_apply(r, f)
      l >>= 1
      r >>= 1
    l = l2
    r = r2
    for i in range(1, self.log + 1):
      if ((l >> i) << i) != l: self.update(l >> i)
      if ((r >> i) << i) != r: self.update((r - 1) >> i)

  def max_right(self, l, g):
    #assert 0 <= l <= self.n
    #assert g(self.e)
    if l == self.n: return self.n
    l += self.size
    for i in range(1, self.log + 1)[::-1]:
      self.push(l >> i)
    sm = self.e
    while True:
      while l % 2 == 0: l >>= 1
      if not g(self.op(sm, self.data[l])):
        while l < self.size:
          self.push(l)
          l = 2 * l
          if g(self.op(sm, self.data[l])):
            sm = self.op(sm, self.data[l])
            l += 1
        return l - self.size
      sm = self.op(sm, self.data[l])
      l += 1
      if (l & -l) == l: return self.n

  def min_left(self, r, g):
    #assert 0 <= r <= self.n
    #assert g(self.e)
    if r == 0: return 0
    r += self.size
    for i in range(1, self.log + 1)[::-1]:
      self.push((r - 1) >> i)
    sm = self.e
    while True:
      r -= 1
      while r > 1 and r % 2: r >>= 1
      if not g(self.op(self.data[r], sm)):
        while r < self.size:
          self.push(r)
          r = 2 * r + 1
          if g(self.op(self.data[r], sm)):
            sm = self.op(self.data[r], sm)
            r -= 1
        return r + 1 - self.size
      sm = self.op(self.data[r], sm)
      if (r & -r) == r: return 0

import sys
input = sys.stdin.buffer.readline

INF = 10**18
MOD = 998244353

N, Q = map(int, input().split())

def op(x, y):
  xv, xr = x
  yv, yr = y
  return (xv + yv) % MOD, (xr + yr) % MOD

def mapping(p, x): #pが更新後の値, xが更新する前の値
  xv, xr = x
  if p != INF:
    return p * xr % MOD, xr
  return x

def composition(p, q):
  if p != INF:
    return p
  return q

lis = [[1,1] for _ in range(N)]
tmp = 1
for i in range(N-2,-1,-1):
  tmp *= 10 
  tmp %= MOD
  lis[i][0], lis[i][1] = tmp, tmp
#print(lis)
#arr = [(e,e) for e in lis]

e = 0
id = INF

lst = LazySegmentTree(N, op, e, mapping, composition, id)
lst.build(lis)

ans = [0]*Q

for i in range(Q):
  l, r, d = map(int, input().split())
  lst.range_apply(l - 1, r, d)
  ans[i] = lst.data[1][0]


print(*ans, sep='\n')