Go: package main

// packed from [splaytree.go main.rmq.go splaytree_debug.go] with goone.

import (
	"bufio"
	"fmt"
	"io"
	"os"
	"strconv"
	"strings"
)

type SplayTreeNode struct {
	parent, left, right *SplayTreeNode

	size    int
	value   int
	minimum int
}

func newSplayTreeNode(v int) *SplayTreeNode {
	return &SplayTreeNode{value: v}
}

func (node *SplayTreeNode) rotate() {
	var pp, p, c *SplayTreeNode
	p = node.parent
	pp = p.parent
	if pp != nil && pp.left == p {
		pp.left = node
	}
	if pp != nil && pp.right == p {
		pp.right = node
	}
	node.parent = pp
	if p.left == node {
		c = node.right
		node.right = p
		p.left = c
	} else {
		c = node.left
		node.left = p
		p.right = c
	}
	p.parent = node
	if c != nil {
		c.parent = p
	}
	p.update()
	node.update()
}

func (node *SplayTreeNode) state() int {
	if node.parent == nil {
		return 0
	}
	if node.parent.left == node {
		return -1
	} else if node.parent.right == node {
		return 1
	}
	return 0
}

func (node *SplayTreeNode) splay() {
	for node.state() != 0 {
		if node.parent.state() == 0 {
			node.rotate()
			return
		}
		if node.state() == node.parent.state() {
			node.parent.rotate()
			node.rotate()
		} else {
			node.rotate()
			node.rotate()
		}
	}
}

func (node *SplayTreeNode) update() {
	node.size = 1
	node.minimum = node.value
	if node.left != nil {
		node.size += node.left.size
		node.minimum = min(node.minimum, node.left.minimum)
	}
	if node.right != nil {
		node.size += node.right.size
		node.minimum = min(node.minimum, node.right.minimum)
	}
}
func min(a, b int) int {
	if a < b {
		return a
	}
	return b
}

func (node *SplayTreeNode) Get(idx int) *SplayTreeNode {
	cur := node
	for true {
		lSize := 0
		if cur.left != nil {
			lSize = cur.left.size
		}
		if idx < lSize {
			cur = cur.left
		}
		if lSize == idx {
			cur.splay()
			return cur
		}
		if idx > lSize {
			cur = cur.right
			idx = idx - lSize - 1
		}
	}
	return nil
}

func mergeSplayNode(l, r *SplayTreeNode) (root *SplayTreeNode) {
	if l == nil {
		return r
	}
	if r == nil {
		return l
	}
	root = l.Get(l.size - 1)
	root.right = r
	r.parent = root
	root.update()
	return root
}

func (node *SplayTreeNode) Split(leftCount int) (left *SplayTreeNode, right *SplayTreeNode) {
	root := node.Get(leftCount)
	lRoot, rRoot := root.left, root

	rRoot.left = nil
	if lRoot != nil {
		lRoot.parent = nil
	}
	rRoot.update()
	return lRoot, rRoot
}

func splayTreeInsert(t *SplayTreeNode, idx int, node *SplayTreeNode) (root *SplayTreeNode) {
	l, r := t.Split(idx)
	return mergeSplayNode(
		mergeSplayNode(l, node),
		r,
	)
}

func removeSplayTree(t *SplayTreeNode, idx int) (root *SplayTreeNode, removed *SplayTreeNode) {
	target := t.Get(idx)
	l, r := target.left, target.right
	if l != nil {
		l.parent = nil
	}
	if r != nil {
		r.parent = nil
	}
	target.left = nil
	target.right = nil
	target.update()
	return mergeSplayNode(l, r), target
}

const (
	initialBufSize = 100000
	maxBufSize     = 1000000
)

var sc *bufio.Scanner

func initScanner(r io.Reader) *bufio.Scanner {
	buf := make([]byte, initialBufSize)

	sc := bufio.NewScanner(r)
	sc.Buffer(buf, maxBufSize)
	sc.Split(bufio.ScanWords)
	return sc
}

func main() {
	sc = initScanner(os.Stdin)
	resolve()
}

func resolve() {
	n := scanInt(sc)
	q := scanInt(sc)
	table := newDebugNodeSlice(200001)
	for i := 0; i < n; i++ {
		table.l[i].parent = &table.l[i+1]
		table.l[i+1].left = &table.l[i]
		table.l[i].update()
		table.l[i+1].update()
	}
	var root *SplayTreeNode = &table.l[n]
	for i := 0; i < n; i++ {
		table.l[i].value = scanInt(sc)
		table.l[i].update()
	}
	table.l[n].update()
	for i := 0; i < q; i++ {
		switch scanInt(sc) {
		case 0:
			root = shift(root, scanInt(sc), scanInt(sc))
		case 1:
			l := scanInt(sc)
			r := scanInt(sc)
			tmp, rRoot := root.Split(r + 1)
			lRoot, cRoot := tmp.Split(l)
			fmt.Println(cRoot.minimum)
			root = mergeSplayNode(mergeSplayNode(lRoot, cRoot), rRoot)
		case 2:
			root = root.Get(scanInt(sc))
			root.value = scanInt(sc)
			root.update()
		}
	}
}


C++: #include <bits/stdc++.h>
using namespace std;
using ll = long long;
using PII = pair<ll, ll>;
#define FOR(i, a, n) for (ll i = (ll)a; i < (ll)n; ++i)
#define REP(i, n) FOR(i, 0, n)
#define ALL(x) x.begin(), x.end()
template<typename T> void chmin(T &a, const T &b) { a = min(a, b); }
template<typename T> void chmax(T &a, const T &b) { a = max(a, b); }
struct FastIO {FastIO() { cin.tie(0); ios::sync_with_stdio(0); }}fastiofastio;
#ifdef DEBUG_ 
#include "../program_contest_library/memo/dump.hpp"
#else
#define dump(...)
#endif
const ll INF = 1LL<<60;

template<class M>
struct RBST {
    using T = typename M::T;
    using E = typename M::E;

    struct node {
        node *l, *r, *p;
        T val;
        E lazy;
        bool rev;
        int sz;

        node(T v, E p) : l(nullptr),r(nullptr),val(v),lazy(p),rev(false),sz(1) {}
    };

    int size(node* a) { return !a ? 0 : a->sz; }
    T val(node* a) { return !a ? M::dt() : (eval(a), a->val); }
    node* fix(node* a) {
        a->sz = size(a->l) + 1 + size(a->r);
        a->val = M::f(val(a->l), a->val, val(a->r));
        return a;
    }
    void eval(node* a) {
        if(a->lazy != M::de()) {
            a->val = M::g(a->val, a->lazy, size(a));
            if(a->l) a->l->lazy = M::h(a->l->lazy, a->lazy);
            if(a->r) a->r->lazy = M::h(a->r->lazy, a->lazy);
            a->lazy = M::de();
        }
        if(a->rev) {
            swap(a->l, a->r);
            if(a->l) a->l->rev ^= 1;
            if(a->r) a->r->rev ^= 1;
            a->rev = false;
        }
    }

    inline int xor128() {
        static int x = 123456789, y = 362436069, z = 521288629, w = 88675123;
        int t;
        t = x ^ (x << 11); x = y; y = z; z = w;
        return w = (w ^ (w >> 19)) ^ (t ^ (t >> 8));
    }

    node* merge(node *a, node *b) {
        if(!a) return b;
        if(!b) return a;
        eval(a); eval(b);
        if(xor128() % (size(a) + size(b)) < size(a)) {
            a->r = merge(a->r, b);
            if(a->r) a->r->p = a;
            return fix(a);
        } else {
            b->l = merge(a, b->l);
            if(b->l) b->l->p = b;
            return fix(b);
        }
    }
    // [0,k) [k,n)
    pair<node*, node*> split(node *a, int k) {
        if(!a) return pair<node*, node*>(nullptr, nullptr);
        eval(a);
        node *sl, *sr;
        if(k <= size(a->l)) {
            tie(sl, sr) = split(a->l, k);
            if(a->l) a->l->p = nullptr;
            a->l = sr;
            if(a->l) a->l->p = a;
            return pair<node*, node*>(sl, fix(a));
        }
        tie(sl, sr) = split(a->r, k - size(a->l) - 1);
        if(a->r) a->r->p = nullptr;
        a->r = sl;
        if(a->r) a->r->p = a;
        return pair<node*, node*>(fix(a), sr);
    }
    // 要素の挿入/削除
    void insert(node*& a, int k, const T& x) {
        node *sl, *sr;
        tie(sl, sr) = split(a, k);
        a = merge(sl, merge(new node(x, M::de()), sr));
    }
    T erase(node*& a, int k) {
        node *sl, *sr, *tl, *tr;
        tie(sl, sr) = split(a, k + 1);
        tie(tl, tr) = split(sl, k);
        a = merge(tl, sr);
        return val(tr);
    }
    // 点更新
    void update(node*& a, int k, const E& x) {
        node *sl, *sr, *tl, *tr;
        tie(sl, sr) = split(a, k + 1);
        tie(tl, tr) = split(sl, k);
        if(tr) tr->val = M::g(tr->val, x, size(tr));
        a = merge(merge(tl, tr), sr);
    }
    // 区間更新
    void update(node*& a, int l, int r, const E& m) {
        node *sl, *sr, *tl, *tr;
        tie(sl, sr) = split(a, r);
        tie(tl, tr) = split(sl, l);
        if(tr) tr->lazy = M::h(tr->lazy, m);
        a = merge(merge(tl, tr), sr);
    }
    // 点取得
    T get(node*& a, int k) {
        node *sl, *sr, *tl, *tr;
        tie(sl, sr) = split(a, k + 1);
        tie(tl, tr) = split(sl, k);
        T res = !tr ? M::dt() : tr->val;
        a = merge(merge(tl, tr), sr);
        return res;
    }
    // 区間クエリ
    T query(node*& a, int l, int r) {
        node *sl, *sr, *tl, *tr;
        tie(sl, sr) = split(a, r);
        tie(tl, tr) = split(sl, l);
        T res = !tr ? M::dt() : tr->val;
        a = merge(merge(tl, tr), sr);
        return res;
    }
    // 区間[l,r)の反転
    void reverse(node*& a, int l, int r) {
        node *sl, *sr, *tl, *tr;
        tie(sl, sr) = split(a, r);
        tie(tl, tr) = split(sl, l);
        if(tr) tr->rev ^= 1;
        a = merge(merge(tl, tr), sr);
    }
    // 頂点aが属する木の根を求める
    node* getroot(node *a) {
        if(!a->p) return a;
        return getroot(a->p);
    }
    // x以上の最小の位置
    int lower_bound(node *t, const T &x) {
        if(!t) return 0;
        if(x <= val(t)) return lower_bound(t->l, x);
        return lower_bound(t->r, x) + size(t->l) + 1;
    }
    // xより大きい最小の位置
    int upper_bound(node *t, const T &x) {
        if(!t) return 0;
        if(x < val(t)) return upper_bound(t->l, x);
        return upper_bound(t->r, x) + RBST<T>::size(t->l) + 1;
    }
    // xの個数
    int count(node *t, const T &x) {
        return upper_bound(t, x) - lower_bound(t, x);
    }
    // k番目の要素を求める
    T kth_element(node *t, int k) {
        if(k < RBST<T>::size(t->l)) return kth_element(t->l, k);
        if(k == RBST<T>::size(t->l)) return val(t);
        return kth_element(t->r, k - RBST<T>::size(t->l) - 1);
    }
    // 要素xを追加する
    void insert_key(node *&t, const T &x) {
        RBST<T>::insert(t, lower_bound(t, x), x);
    }
    // 要素xを消す
    void erase_key(node *&t, const T &x) {
        if(!count(t, x)) return;
        RBST<T>::erase(t, lower_bound(t, x));
    }
    // デバッグ用
    void debug(node* t) {
        if(t == nullptr) return;
        cerr << "{";
        debug(t->l);
        cerr << " " << t->val << " ";
        debug(t->r);
        cerr << "}";
    }
};

int main() {
    int n, q;
    cin >> n >> q;

    struct update_min {
        using T = PII;
        using E = ll;
        static T dt() { return PII(INT_MAX, INT_MAX); }
        static constexpr E de() { return INT_MAX; }
        static T f(const T &l, const T &self, const T &r) {
            return PII(self.first, min({l.second, self.first, r.second}));
        }
        static T g(const T &a, const E &b, const int &sz) {
            return b == de() ? a : PII(b, b);
        }
        static E h(const E &a, const E &b) {
            return b == de() ? a : b;
        }
    };
    RBST<update_min> tree;
    RBST<update_min>::node* root = nullptr;

    REP(i, n) {
        int a;
        cin >> a;
        tree.insert(root, i, PII(a, a));
    }

    REP(i, q) {
        int t, l, r; cin >> t >> l >> r;
        if(t == 0) {
            // [0,l)[l,r)[r,r+1)[r+1,n)
            auto p1 = tree.split(root, r+1);
            auto p2 = tree.split(p1.first, r);
            auto p3 = tree.split(p2.first, l);
            root = tree.merge(tree.merge(tree.merge(p3.first, p2.second), p3.second), p1.second); 
        } else if(t == 1) {
            cout << tree.query(root, l, r+1).second << endl;
        } else if(t == 2) {
            tree.update(root, l, r);
        }
    }

    return 0;
}