Problem:
RMQ and RAQ

Write a program which manipulates a sequence $A$ = {$a_0, a_1, ..., a_{n-1}$} with the following operations:

$add(s, t, x)$ : add $x$ to $a_s, a_{s+1}, ..., a_t$.

$find(s, t)$ : report the minimum value in $a_s, a_{s+1}, ..., a_t$.

Note that the initial values of $a_i ( i = 0, 1, ..., n-1 )$ are 0.

Input

$n$ $q$
$query_1$
$query_2$
:
$query_q$

In the first line, $n$ (the number of elements in $A$) and $q$ (the number of queries) are given. Then, $i$th query $query_i$ 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 $add(s, t, x)$ and '1' denotes $find(s, t)$.

Output

For each $find$ query, print the minimum value.

Constraints

$1 ≤ n ≤ 100000$

$1 ≤ q ≤ 100000$

$0 ≤ s ≤ t < n$

$-1000 ≤ x ≤ 1000$

Sample Input 1

6 7
0 1 3 1
0 2 4 -2
1 0 5
1 0 1
0 3 5 3
1 3 4
1 0 5

Sample Output 1

-2
0
1
-1

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（回答）」の場合）

	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) => 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;

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

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

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

*/