using System;
using System.Collections;
using System.IO;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Collections.Generic;
using System.Threading.Tasks;

class Program
{
	/*

・概要
	遅延評価を行い、必要なタイミングで一気に値を更新するLazySegmentTree。

	cf. セグメントツリーとは
	「完全二分木（全ての葉の深さが等しい木）によって実装された、区間を扱うのに適したデータ構造
	区間に対する操作を対数時間 O(log N) で行える」
	（要素数・クエリ数がともに10^5あっても余裕で間に合う）

・使用法
	1. 問題に合わせ、コンストラクタの引数を設定（ラムダ式を使えば良い）
	「更新クエリと回答クエリの組み合わせ」によって引数が全く変わってくるので注意。
	（引数の解説については最下部を見よ）

	（cf.「RAQ（更新）+ RMQ（回答）」などの基本的組み合わせについては、最下部にテンプレートを用意
	　→コピペして使ってね）

	（e.g.「RAQ（更新）+ RMQ（回答）」の場合
		Func<long, long, long> fx = (x1,x2) => x1 + x2;
		Func<long, long, long> fa = (x,m) => x + m;
		Func<long, long, long> fm = (m1,m2) => m1 + m2;
		Func<long, long, long> fp = (m,n) => m * n;
		long init = 0;
		long lazyInit = 0;）

	2. LazySegmentTreeクラスのオブジェクトを作成
	（e.g. LazySegmentTree st = new LazySegmentTree(N);）

	3. セグメントツリーの初期化（元の配列を渡す）
	（e.g. st.Initialize(arr);）

	--- 以下は、どの問題でもやることは同じ（更新 or 回答） ---

	4a. 適宜、更新クエリに応答（閉区間[s,t]について、更新クエリに答える）
	（e.g. st.Update(s,t,x);）

	4b. 適宜、回答クエリに応答（閉区間[s,t]について、回答クエリに答える）
	（e.g. long ans = st.GetAnswer(s,t);）

	*/

	//Main
	static void Main(string[] args)
	{
		//読み込む
		string[] S = Console.ReadLine().Split(' ');
		//要素数
		int N = int.Parse(S[0]);
		//区間数
		int K = int.Parse(S[1]);

		//mod
		long mod = 998244353;

		//1. 問題に合わせ、コンストラクタの引数を設定（ラムダ式を使えば良い）
		Func<long, long, long> fx = (x1,x2) => (x1 + x2) % mod;
		Func<long, long, long> fa = (x,m) => (x + m) % mod;
		Func<long, long, long> fm = (m1,m2) => (m1 + m2) % mod;
		Func<long, long, long> fp = (m,n) => m * n;
		long init = 0;
		long lazyInit = 0;

		//2. LazySegmentTreeクラスのオブジェクトを作成
		/*
		引数は順に、
		元の配列の要素数、関数4つ、二分木の初期値、遅延評価配列の初期値（詳しくは最下部を見よ）
		*/
		LazySegmentTree st = new LazySegmentTree(N,fx,fa,fm,fp,init,lazyInit);

		//初期状態の配列を作成（この問題では、2^31-1）
		long[] arr = new long[N];
		arr[0] = 1;

		//3. セグメントツリーの初期化（元の配列を渡す）
		st.Initialize(arr);

		//区間を読み込む
		int[,] range = new int[K,2];
		for(int i=0; i<K; i++){
			string[] line = Console.ReadLine().Split(' ');
			int L = int.Parse(line[0]);
			int R = int.Parse(line[1]);
			range[i,0] = L;
			range[i,1] = R;
		}

		//下から順番に更新
		for(int i=0; i<N; i++){
			//現在のi番目の値を取得
			long tempo = st.GetAnswer(i,i) % mod;

			//debug
			//Console.WriteLine("i={0},tempo={1}",i,tempo);

			//各区間を更新
			for(int j=0; j<K; j++){
				//更新対象の左端
				int L = range[j,0]+i;
				if(L >= N){
					continue;
				}
				//右端
				int R = Math.Min(N-1, range[j,1]+i);

				//Console.Write("区間[{0},{1}] / ",L,R);

				//この区間（閉区間）に対し、tempoだけ加算する
				st.Update(L,R,tempo);
			}
			//Console.WriteLine("");
		}

		//N-1番目の値を取得
		long ans = st.GetAnswer(N-1,N-1) % mod;
		if(ans < 0){
			ans -= mod;
		}

		//出力
		Console.WriteLine(ans);

	}
}


//セグメントツリーのクラス
/*
配列の中身はlong確としておく。
*/
class LazySegmentTree
{
	/*
	メンバー変数
	*/
	//更新や取得で使用するメソッド（long引数2つで、long戻り値）
	private Func<long, long, long> fx;
	private Func<long, long, long> fa;
	private Func<long, long, long> fm;
	private Func<long, long, long> fp;
	//完全二分木の初期値（単位元）
	private long INIT;
	//遅延評価配列の初期値（単位元）
	private long lazyINIT;
	//葉の数（※「葉」とは、末端（最下部）のノードのこと。元の配列の各値 or 初期値を保持）
	private int leaf;
	//完全二分木の配列
	private long[] tree;
	//遅延評価用の配列（一時的に値を保存）
	private long[] lazy;

	/*
	（１）初期化
	*/

	//コンストラクタ
	/*
	引数は要素数、関数4つ、二分木の初期値、遅延評価配列の初期値
	*/
	public LazySegmentTree(int len, Func<long, long, long> f1, Func<long, long, long> f2,
	Func<long, long, long> f3, Func<long, long, long> f4, long ex, long em){
		//完全二分木の要素数はn^2である必要
		/*
		n^2 >= lenとなるような最小のn^2（=x）を求める
		*/
		int x = 1;
		while(len > x){
			x *= 2;
		}
		this.leaf = x;

		//関数の設定
		this.fx = f1;
		this.fa = f2;
		this.fm = f3;
		this.fp = f4;

		//初期値の設定
		this.INIT = ex;
		this.lazyINIT = em;

		//treeの初期化
		//treeの要素数は、葉の数 * 2 - 1となる
		this.tree = new long[2*x-1];
		for(int i=0; i<2*x-1; i++){
			this.tree[i] = this.INIT;
		}

		//lazyの初期化
		this.lazy = new long[2*x-1];
		for(int i=0; i<2*x-1; i++){
			this.lazy[i] = this.lazyINIT;
		}
	}

	//元の配列に合わせ、完全二分木を初期化するメソッド
	/*
	計算量はO(N)だが、この段階ではまだ回答クエリに応答できないので注意。
	*/
	public void Initialize(long[] arr){
		//元の配列の値をSet
		for(int i=0; i<arr.Length; i++){
			this.tree[i + this.leaf - 1] = arr[i];
		}

		//その上で、木構造をBuild
		for(int k=this.leaf-2; k>=0; k--){
			this.tree[k] = this.fx(this.tree[2*k+1],this.tree[2*k+2]);
		}
	}

	/*
	（２）値の更新と、遅延評価（更新クエリへの応答）
	*/

	//更新に使う遅延評価メソッド
	/*
	引数は、現在の対象要素currentと、問題となる区間の長さlen
	*/
	void Eval(int current, int len){
		//初期値の場合、更新は不要
		if(this.lazy[current] == this.lazyINIT){
			return;
		}

		//葉でない場合には、子に伝播
		if(current < this.leaf - 1){
			this.lazy[current*2+1] = this.fm(this.lazy[current*2+1],this.lazy[current]);
			this.lazy[current*2+2] = this.fm(this.lazy[current*2+2],this.lazy[current]);
		}

		//自身を更新
		//cf. fp関数を使うのはここだけ
		this.tree[current] = this.fa(this.tree[current],this.fp(this.lazy[current],len));

		//初期状態に戻す
		this.lazy[current] = this.lazyINIT;
	}


	//値の追加用のメソッド
	/*
	引数は、対象たる閉区間[left, right]
	*/
	public void Update(int left, int right, long val){
		//実装の都合上、半開区間[left, right)に直す
		right ++;

		//更新を行う
		UpdateSub(left, right, val, 0, 0, this.leaf);
	}

	//値の追加用のサブメソッド
	/*
	left, rightは、元々のクエリの対象区間
	（※葉の番号（元の配列のインデックス）であって、treeのインデックスではない）
	currentは、現在見ているノードのインデックス（現在回答しているノードくん）
	tL, tRは、currentノードくんが対象としている最小値区間（半開区間）の範囲
	（※tLとtRも葉の番号（treeのインデックスではない））
	*/
	void UpdateSub(int left, int right, long val, int current, int tL, int tR){
		//現在のノードを評価
		this.Eval(current, tR-tL);

		//1. クエリが対象範囲外の場合、何もしない
		if(tR <= left || right <= tL){
			return;
		}
		//2. 完全に範囲内に含んでいる場合、遅延配列に値を入れた後に評価
		if(left <= tL && tR <= right){
			this.lazy[current] = this.fm(this.lazy[current],val);
			Eval(current, tR-tL);
		}
		//3. 一部区間だけがカブっている場合（左右どちらかがはみ出ている）
		else{
			//左側の子の更新
			UpdateSub(left, right, val, current*2+1, tL, (tL+tR)/2);
			//右側の子の更新
			UpdateSub(left, right, val, current*2+2, (tL+tR)/2, tR);
			//現在のノードを更新
			this.tree[current] = this.fx(this.tree[current*2+1],this.tree[current*2+2]);
		}
	}

	/*
	（３）値の取得（回答クエリへの応答）
	*/

	//閉区間[left, right]に於ける答えを返す
	public long GetAnswer(int left, int right){
		//実装の都合上、半開区間に直す
		right ++;

		//実装の都合上、サブタスクに分ける
		/*
		まずは根ノード（インデックス0）に問い合わせる。
		根ノードの対象範囲は、全ての葉（葉番号0 ~ leaf-1まで）
		*/
		return GetAnswerSub(left, right, 0, 0, this.leaf);
	}

	//サブタスク（cf. 引数は、UpdateSubと同じ）
	long GetAnswerSub(int left, int right, int current, int tL, int tR){
		//まず更新を行う
		this.Eval(current, tR-tL);

		//1. クエリが対象範囲外の場合、初期値を返す
		if(tR <= left || right <= tL){
			return INIT;
		}
		//2. 完全に範囲内に含んでいる場合、自身の値を返す
		else if(left <= tL && tR <= right){
			return this.tree[current];
		}
		//3. 一部区間だけがカブっている場合（左右どちらかがはみ出ている）
		else{
			//左側の子の回答
			long valL = GetAnswerSub(left, right, current*2+1, tL, (tL+tR)/2);
			//右側の子の回答
			long valR =	GetAnswerSub(left, right, current*2+2, (tL+tR)/2, tR);
			//両者の回答から自身の回答を決定
			return this.fx(valL, valR);
		}
	}


	/*
	--- ---
	*/

	//debug用の、完全二分木出力メソッド
	public void DebugWrite(){
		for(int i=0; i<this.tree.Length; i++){
			Console.Write("t[{0}]={1}",i,this.tree[i]);
			//葉に該当する場合、葉の番号iを出力
			if(i >= leaf-1){
				Console.Write("(i={0}),", i-this.leaf+1);
			}
			//それ以外
			else{
				Console.Write(",");
			}
		}
		Console.WriteLine("");
	}
}


/*
--- おまけ（凡例）---

よくある問題に対する、コンストラクタの引数設定値のセット
（cf. i番目の「単一の」値を更新 or 取得する場合、
	単に閉区間を[i,i]としてメソッドを実行すれば良いだけ。）


（１）RAQ（更新） + RSQ（回答）の場合（加算→和）
（Range Add Query と Range Sum Query：区間加算と区間和）

	Func<long, long, long> fx = (x1,x2) => x1 + x2;
	Func<long, long, long> fa = (x,m) => x + m;
	Func<long, long, long> fm = (m1,m2) => m1 + m2;
	Func<long, long, long> fp = (m,n) => m * n;
	long init = 0;
	long lazyInit = 0;

（２）RAQ（更新） + RMQ（回答）の場合（加算→最小値）
（Range Add Query と Range Minimum Query：区間加算と区間最小値）

	Func<long, long, long> fx = (x1,x2) => Math.Min(x1,x2);
	Func<long, long, long> fa = (x,m) => x + m;
	Func<long, long, long> fm = (m1,m2) => m1 + m2;
	Func<long, long, long> fp = (m,n) => m;
	long init = long.MaxValue;
	long lazyInit = 0;

	（cf. 参考（区間加算と区間「最大」値の場合（加算→最大値））
		Func<long, long, long> fx = (x1,x2) => Math.Max(x1,x2);
		Func<long, long, long> fa = (x,m) => x + m;
		Func<long, long, long> fm = (m1,m2) => m1 + m2;
		Func<long, long, long> fp = (m,n) => m;
		long init = -long.MaxValue;
		long lazyInit = 0;
		）

（３）RUQ（更新） + RSQ（回答）の場合（上書き→和）
（Range Update Query と Range Sum Query：区間上書きと和）

	Func<long, long, long> fx = (x1,x2) => x1 + x2;
	Func<long, long, long> fa = (x,m) => m;
	Func<long, long, long> fm = (m1,m2) => m2;
	Func<long, long, long> fp = (m,n) => m * n;
	long init = 0;
	long lazyInit = long.MaxValue;

（４）RUQ（更新） + RMQ（回答）の場合（上書き→最小値）
（Range Update Query と Range Minimum Query：区間上書きと区間最小値）

	Func<long, long, long> fx = (x1,x2) => Math.Min(x1,x2);
	Func<long, long, long> fa = (x,m) => m;
	Func<long, long, long> fm = (m1,m2) => m2;
	Func<long, long, long> fp = (m,n) => m;
	long init = long.MaxValue;
	long lazyInit = long.MaxValue;


--- ---
cf. それぞれの引数は、一般に...（不正確な理解だが...）

1. 関数fxは、求めたい値に関わる（「回答」クエリ：セグメント木のデータの値xの二項演算）
	e.g.
	区間和クエリの場合（RSQ）： Func<long, long, long> fx = (x1,x2) => x1 + x2;
	区間最小値クエリの場合（RMQ）： Func<long, long, long> fx = (x1,x2) =>  Math.Min(x1,x2);

2. 関数faは、値の更新方法に関わる（「更新」クエリ：x, m両値の二項演算）
	e.g.
	区間加算クエリの場合（RAQ）： Func<long, long, long> fa = (x,m) => x + m;
	区間上書きクエリの場合（RUQ）： Func<long, long, long> fa = (x,m) => m;

3. 関数fmは、値の更新方法に関わる（「更新」クエリ：遅延伝播の値mの二項演算）
	e.g.
	区間加算クエリの場合（RAQ）： Func<long, long, long> fm = (m1,m2) => m1 + m2;
	区間上書きクエリの場合（RUQ）： Func<long, long, long> fm = (m1,m2) => m2;

4. 関数fpは、求めたい値に関わる（「回答」クエリ：mの写像（何回演算するか））
	e.g.
	区間和クエリの場合（RSQ）： Func<long, long, long> fp = (m,n) => m * n;
	区間最小値クエリの場合（RMQ）： Func<long, long, long> fp = (m,n) => m;

	cf. 区間の長さが関係ない問題の場合、fp関数は実質的には無意味
	　→fp関数の2つ目の引数は区間長なので、それを無効化すれば良い（m * 1と同じ）
	　（Func<long, long, long> fp = (m,n) => m;）

	（※例えば、RMQを求める場合（２や４）には、区間長は関係ない）

5. 初期値init, lazyInitは、それぞれx, mの「単位元」を表す
	cf. ここでの単位元とは、計算結果に影響を及ぼさない初期値
	（e.g. 加算なら0, 乗算なら1, 最小値なら無限大（inf）。
		上書きの場合には、未更新を示す初期値としての無限大（inf：存在しない値の代わり）。）

	a.初期値initは、求めたい値に関わる（「回答」クエリ）
		e.g.
		区間和クエリの場合（RSQ）：	long init = 0;
		区間最小値クエリの場合（RMQ）： long init = long.MaxValue;

	b.初期値lazyInitは、値の更新方法に関わる（「更新」クエリ）
		e.g.
		区間加算クエリの場合（RAQ）：long lazyInit = 0;
		区間上書きクエリの場合（RUQ）：long lazyInit = long.MaxValue;
--- ---

*/

