Problem:
RMQ and RUQ

Write a program which manipulates a sequence A = {a0, a1, . . . , an−1} with the following operations:

update(s, t, x): change as, as+1, ..., at to x.

find(s, t): report the minimum element in as, as+1, ..., at.

Note that the initial values of ai (i = 0, 1, . . . , n−1) are 231-1.

Input

n q
query1
query2
:
queryq

In the first line, n (the number of elements in A) and q (the number of queries) are given. Then, ith query queryi is given in the following format:

0 s t x

or

1 s t

The first digit represents the type of the query. '0' denotes update(s, t, x) and '1' denotes find(s, t).

Output

For each find operation, print the minimum value.

Constraints

1 ≤ n ≤ 100000

1 ≤ q ≤ 100000

0 ≤ s ≤ t < n

0 ≤ x < 231−1

Sample Input 1

3 5
0 0 1 1
0 1 2 3
0 2 2 2
1 0 2
1 1 2

Sample Output 1

1
2

Sample Input 2

1 3
1 0 0
0 0 0 5
1 0 0

Sample Output 2

2147483647
5

Solution in C# (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
{
	/*

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

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

・使用法
	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);）

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

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

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

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

	*/

	//Main
	static void Main(string[] args)
	{
		//出力準備（AutoFlushを切る）
		var sw = new StreamWriter(Console.OpenStandardOutput()){AutoFlush = false};
		Console.SetOut(sw);
		//解答
		Solve();
		//出力終了
		Console.Out.Flush();
	}

	static void Solve()
	{
		//読み込む
		string[] S = Console.ReadLine().Split(' ');
		//要素数
		int N = int.Parse(S[0]);
		//クエリ数
		int Q = int.Parse(S[1]);

		//1. 問題に合わせ、コンストラクタの引数を設定（ラムダ式を使えば良い）
		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;

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

		//初期状態の配列を作成（この問題では、2^31-1）
		long[] arr = new long[N];
		for(int i=0; i<N; i++){
			arr[i] = 2147483647;
		}

		//3. 配列の初期化（元の配列を渡す）
		st.Initialize(arr);

		//4.入力に応じて、更新 or 回答を行う
		for(int i=0; i<Q; i++){
			//読み込む
			string[] S1 = Console.ReadLine().Split(' ');
			//クエリの種類
			int com = int.Parse(S1[0]);

			//更新クエリの場合
			if(com == 0){
				//値の取得
				int s = int.Parse(S1[1]);
				int t = int.Parse(S1[2]);
				int x = int.Parse(S1[3]);

				//閉区間[s,t]について、更新クエリに答える
				st.Update(s,t,x);
			}
			//回答クエリの場合
			else{
				//値の取得
				int s = int.Parse(S1[1]);
				int t = int.Parse(S1[2]);

				//閉区間[s,t]について、回答クエリに答える
				long ans = st.GetAnswer(s,t);
				//出力
				Console.WriteLine(ans);
			}

			/*cf. debug用の、完全二分木出力メソッド
			st.DebugWrite();
			*/
		}
	}
}


//セグメントツリーのクラス
/*
配列の中身は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-1);

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

/*
--- おまけ ---

よくある問題に対する、コンストラクタの引数のセット
（引数は上からそれぞれ、
元の配列の要素数、関数4つ、二分木の初期値、遅延評価配列の初期値）

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

（１）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;

（３）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;

*/