#include <cstdio>
#include <vector>
#include <algorithm>
using namespace std;

// Abstract Lazy Segment Tree (抽象・汎化遅延セグメント木)
// Verified: AOJ のセグ木 verify 問全部

// vector 版 (HL 分解に載せるときとかに使おう)
template<typename T>
struct lazysegtree {
    // ノード、単位元
    T I_upd, I_qry;
    vector<T> node, lazy;
    vector<bool> need_upd;
    int SIZE;

    // オペレーション (update, query の 2 つが必要？)
    // update function は範囲を指定する形にしよう
    // upd_f(X, Y, l, r) -> 範囲が [l, r) であるようなノード X に Y を反映！
    // lazy について update するときは範囲を 1 にしないとバグります
    T (*upd_f)(T, T, int, int), (*qry_f)(T, T);

    // 演算子と単位元をセットし、全ての node と lazy を単位元で初期化
    lazysegtree(int seg_size, T (*op1)(T, T, int, int), T (*op2)(T, T), T X_upd, T X_qry) {
        upd_f = op1;
        qry_f = op2;
        I_upd = X_upd, I_qry = X_qry;
        SIZE = 1; while(SIZE < seg_size) SIZE *= 2;
        node     = vector<T> (2*SIZE, I_qry);
        lazy     = vector<T> (2*SIZE, I_upd);
        need_upd = vector<bool>(2*SIZE, false);
    }

    // 配列 vec の値で初期化
    void init(vector<T> vec) {
        int N = (int)vec.size();
        for(int i=0; i<N; i++) {
            node[SIZE-1+i] = vec[i];
        }
        for(int i=SIZE-2; i>=0; i--) {
            node[i] = qry_f(node[2*i+1], node[2*i+2]);
        }
    }

    void lazy_eval(int k, int l, int r) {
        if(!need_upd[k]) return;
        node[k] = upd_f(node[k], lazy[k], l, r);
        if(r - l > 1) {
            lazy[2*k+1] = upd_f(lazy[2*k+1], lazy[k], 0, 1);
            lazy[2*k+2] = upd_f(lazy[2*k+2], lazy[k], 0, 1);
            need_upd[2*k+1] = need_upd[2*k+2] = true;
        }
        lazy[k] = I_upd;
        need_upd[k] = false;
    }

    // 半開区間 [a, b) に対して値 val を反映させる
    // (upd_f を用いて処理)
    void update(int a, int b, T val, int l=0, int r=-1, int k=0) {
        if(r < 0) r = SIZE;
        lazy_eval(k, l, r);
        if(b <= l || r <= a) return;
        if(a <= l && r <= b) {
            lazy[k] = upd_f(lazy[k], val, 0, 1);
            need_upd[k] = true;
            lazy_eval(k, l, r);
        }
        else {
            int mid = (l + r) / 2;
            update(a, b, val, l, mid, 2*k+1);
            update(a, b, val, mid, r, 2*k+2);
            node[k] = qry_f(node[2*k+1], node[2*k+2]);
        }
    }

    // 半開区間 [a, b) に対してクエリを投げる
    // (qry_f を用いて処理)
    T query(int a, int b, int l=0, int r=-1, int k=0) {
        if(r < 0) r = SIZE;
        lazy_eval(k, l, r);
        if(b <= l || r <= a) return I_qry;
        if(a <= l && r <= b) return node[k];
        int mid = (l + r) / 2;
        T vl = query(a, b, l, mid, 2*k+1);
        T vr = query(a, b, mid, r, 2*k+2);
        return qry_f(vl, vr);
    }
};

// [[つかいかた]]
// update 用と query 用の関数と単位元 (関数を噛ませても結果が変わらないもの) を用意して宣言する
// 下の例だと、update 用の関数が upd で、query 用の関数が fnd で、単位元が 0

typedef long long int ll;
const ll INF = 1LL << 60;
// 範囲が [l, r) のノードの値 a に値 b を反映させる
ll upd(ll a, ll b, int l, int r) {return a + b;}
// ノードの値 a と b に対してどのような操作をするか？
ll fnd(ll a, ll b) {return min(a, b);}

int main() {
    int N, Q; scanf("%d%d", &N, &Q);
    lazysegtree<ll> seg(N, upd, fnd, 0, INF);
    seg.init(vector<ll>(N, 0));
    for(int i=0; i<Q; i++) {
        int query; scanf("%d", &query);
        if(query == 0) {
            int s, t, x; scanf("%d%d%d", &s, &t, &x);
            seg.update(s, t+1, x);
        }
        if(query == 1) {
            int s, t; scanf("%d%d", &s, &t);
            printf("%lld\n", seg.query(s, t+1));
        }
    }
}


Measured memory_bytes: 7152