Problem:
Score : 400 points

Problem Statement

There are N cells arranged in a row, numbered 1, 2, \ldots, N from left to right.

Tak lives in these cells and is currently on Cell 1. He is trying to reach Cell N by using the procedure described below.

You are given an integer K that is less than or equal to 10, and K non-intersecting segments [L_1, R_1], [L_2, R_2], \ldots, [L_K, R_K].
Let S be the union of these K segments.
Here, the segment [l, r] denotes the set consisting of all integers i that satisfy l \leq i \leq r.

When you are on Cell i, pick an integer d from S and move to Cell i + d. You cannot move out of the cells.

To help Tak, find the number of ways to go to Cell N, modulo 998244353.

Constraints

2 \leq N \leq 2 \times 10^5

1 \leq K \leq \min(N, 10)

1 \leq L_i \leq R_i \leq N

[L_i, R_i] and [L_j, R_j] do not intersect (i \neq j)

All values in input are integers.

Input

Input is given from Standard Input in the following format:

N K
L_1 R_1
L_2 R_2
:
L_K R_K

Output

Print the number of ways for Tak to go from Cell 1 to Cell N, modulo 998244353.

Sample Input 1

5 2
1 1
3 4

Sample Output 1

4

The set S is the union of the segment [1, 1] and the segment [3, 4], therefore S = \{ 1, 3, 4 \} holds.

There are 4 possible ways to get to Cell 5:

1 \to 2 \to 3 \to 4 \to 5,

1 \to 2 \to 5,

1 \to 4 \to 5 and

1 \to 5.

Sample Input 2

5 2
3 3
5 5

Sample Output 2

0

Because S = \{ 3, 5 \} holds, you cannot reach to Cell 5.
Print 0.

Sample Input 3

5 1
1 2

Sample Output 3

5

Sample Input 4

60 3
5 8
1 3
10 15

Sample Output 4

221823067

Note that you have to print the answer modulo 998244353.

Solution in C# (Mono-mcs 6.8.0.105) (judge verdict: Accepted)

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
{
	//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;
		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);

			//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);
		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("");
	}
}