#include <bits/stdc++.h>
using namespace std;
#if __has_include(<boost/multiprecision/cpp_int.hpp>)
	#include <boost/multiprecision/cpp_int.hpp>
	#include <boost/multiprecision/cpp_dec_float.hpp>
	using bll = boost::multiprecision::cpp_int;
	using bdouble = boost::multiprecision::number<boost::multiprecision::cpp_dec_float<100>>;
	using namespace boost::multiprecision;
#endif
#if __has_include(<boost/stacktrace.hpp>)
	#define BOOST_STACKTRACE_USE_ADDR2LINE
	#define BOOST_STACKTRACE_ADDR2LINE_LOCATION /usr/local/opt/binutils/bin/addr2line
	#define _GNU_SOURCE 1
	#include <boost/stacktrace.hpp>
#endif
#ifdef LOCAL_TEST
	namespace std {
		template<typename T> class dvector : public std::vector<T> {
		public:
			dvector() : std::vector<T>() {}
			explicit dvector(size_t n, const T& value = T()) : std::vector<T>(n, value) {}
			dvector(const std::vector<T>& v) : std::vector<T>(v) {}
			dvector(const std::initializer_list<T> il) : std::vector<T>(il) {}
			dvector(const std::string::iterator first, const std::string::iterator last) : std::vector<T>(first, last) {}
			dvector(const typename std::vector<T>::iterator first, const typename std::vector<T>::iterator last) : std::vector<T>(first, last) {}
			dvector(const typename std::vector<T>::reverse_iterator first, const typename std::vector<T>::reverse_iterator last) : std::vector<T>(first, last) {}
			dvector(const typename std::vector<T>::const_iterator first, const typename std::vector<T>::const_iterator last) : std::vector<T>(first, last) {}
			dvector(const typename std::vector<T>::const_reverse_iterator first, const typename std::vector<T>::const_reverse_iterator last) : std::vector<T>(first, last) {}
			T& operator[](size_t n) {
				try { return this->at(n); } catch (const std::exception& e) { std::cerr << boost::stacktrace::stacktrace() << '\n'; return this->at(n); }
			}
			const T& operator[](size_t n) const {
				try { return this->at(n); } catch (const std::exception& e) { std::cerr << boost::stacktrace::stacktrace() << '\n'; return this->at(n); }
			}
		};
	}
	class dbool {
	private:
		bool boolvalue;
	public:
		dbool() : boolvalue(false) {}
		dbool(bool b) : boolvalue(b) {}
		operator bool&() { return boolvalue; }
		operator const bool&() const { return boolvalue; }
	};
	#define vector dvector
	#define bool dbool
#endif
#ifdef LOCAL_DEV
	template<typename T> std::ostream& operator<<(std::ostream& s, const std::vector<T>& v) {
		for (size_t i = 0; i < v.size(); ++i){ s << v[i]; if (i < v.size() - 1) s << "\t"; } return s; }
	template<typename T> std::ostream& operator<<(std::ostream& s, const std::vector<std::vector<T>>& vv) {
		s << "\\\n"; for (size_t i = 0; i < vv.size(); ++i){ s << vv[i] << "\n"; } return s; }
	template<typename T> std::ostream& operator<<(std::ostream& s, const std::deque<T>& v) {
		for (size_t i = 0; i < v.size(); ++i){ s << v[i]; if (i < v.size() - 1) s << "\t"; } return s; }
	template<typename T> std::ostream& operator<<(std::ostream& s, const std::set<T>& se) {
		s << "{ "; for (auto itr = se.begin(); itr != se.end(); ++itr){ s << (*itr) << "\t"; } s << "}"; return s; }
	template<typename T> std::ostream& operator<<(std::ostream& s, const std::multiset<T>& se) {
		s << "{ "; for (auto itr = se.begin(); itr != se.end(); ++itr){ s << (*itr) << "\t"; } s << "}"; return s; }
	template <typename T, size_t N> std::ostream& operator<<(std::ostream& s, const std::array<T, N>& a) {
		s << "{ "; for (size_t i = 0; i < N; ++i){ s << a[i] << "\t"; } s << "}"; return s; }
	template<typename T1, typename T2> std::ostream& operator<<(std::ostream& s, const std::map<T1, T2>& m) {
		s << "{\n"; for (auto itr = m.begin(); itr != m.end(); ++itr){ s << "\t" << (*itr).first << " : " << (*itr).second << "\n"; } s << "}"; return s; }
	template<typename T1, typename T2> std::ostream& operator<<(std::ostream& s, const std::pair<T1, T2>& p) {
		return s << "(" << p.first << ", " << p.second << ")"; }
	class SIGFPE_exception : std::exception {};
	class SIGSEGV_exception : std::exception {};
	void catch_SIGFPE([[maybe_unused]] int e) { std::cerr << boost::stacktrace::stacktrace() << '\n'; throw SIGFPE_exception(); }
	void catch_SIGSEGV([[maybe_unused]] int e) { std::cerr << boost::stacktrace::stacktrace() << '\n'; throw SIGSEGV_exception(); }
	signed convertedmain();
	signed main() { signal(SIGFPE, catch_SIGFPE); signal(SIGSEGV, catch_SIGSEGV); return convertedmain(); }
	#define main() convertedmain()
	void debug_impl() { std::cerr << '\n'; }
	template<typename Head, typename... Tail> void debug_impl(Head head, Tail... tail) { std::cerr << " " << head << (sizeof...(tail) ? "," : ""); debug_impl(tail...); }
	#define debug(...) do { std::cerr << "(" << #__VA_ARGS__ << ") ="; debug_impl(__VA_ARGS__); } while (false)
#else
	#define debug(...) do {} while (false)
#endif
//#define int long long
using ll = long long;
//constexpr int INF = (ll)1e9 + 7;//INT_MAX=(1<<31)-1=2147483647
constexpr ll INF = (ll)1e18;//(1LL<<63)-1=9223372036854775807
constexpr ll MOD = (ll)1e9 + 7;
constexpr double EPS = 1e-9;
constexpr ll dx[4] = {1, 0, -1, 0};
constexpr ll dy[4] = {0, 1, 0, -1};
constexpr ll dx8[8] = {1, 0, -1, 0, 1, 1, -1, -1};
constexpr ll dy8[8] = {0, 1, 0, -1, 1, -1, 1, -1};
#define rep(i, n)   for(ll i=0, i##_length=(n); i< i##_length; ++i)
#define repeq(i, n) for(ll i=1, i##_length=(n); i<=i##_length; ++i)
#define rrep(i, n)   for(ll i=(n)-1; i>=0; --i)
#define rrepeq(i, n) for(ll i=(n)  ; i>=1; --i)
#define all(v) (v).begin(), (v).end()
#define rall(v) (v).rbegin(), (v).rend()
void p() { std::cout << '\n'; }
template<typename Head, typename... Tail> void p(Head head, Tail... tail) { std::cout << head << (sizeof...(tail) ? " " : ""); p(tail...); }
template<typename T> inline void pv(std::vector<T>& v) { for(ll i=0, N=v.size(); i<N; i++) std::cout << v[i] << " \n"[i==N-1]; }
template<typename T> inline bool chmax(T& a, T b) { return a < b && (a = b, true); }
template<typename T> inline bool chmin(T& a, T b) { return a > b && (a = b, true); }
template<typename T> inline void uniq(std::vector<T>& v) { v.erase(std::unique(v.begin(), v.end()), v.end()); }

/*-----8<-----template-----8<-----*/

/*
RandomizedBinarySearchTree(n, f, M1):= ノードを n 個用意する。
　f は2つの区間の要素をマージする二項演算,
　M1 はモノイドの単位元である。
RandomizedBinarySearchTree(n, f, g, h, M1, OM0):= ノードを n 個用意する。
　f は2つの区間の要素をマージする二項演算, 
　g は要素と作用素をマージする二項演算(第三引数は対応する区間の長さ), 
　h は作用素同士をマージする二項演算, 
　M1 はモノイドの単位元, 
　OM0 は作用素の単位元である。
merge(l, r):= 木 l と木 r を併合する。
split(t, k):= 木 t を [0,k)[k,n) で分割する。
build(v):= 配列 v をもとに木を構築する(O(n))。
dump(r):= 木 r の各ノードを通りがけ順に格納したものを返す(O(n))。
insert(t, k, v):= 木 t の位置 k にノード v を挿入する。
erase(t, k):= 木 t の位置 k のノードを削除する。
query(t, l, r):= 木 t の区間 [l,r) の値を二項演算した結果を返す。
set_propagate(t, a, b, p):= 木 t の区間 [a,b) に作用素 p を適用する。
set_element(t, k, x):= 木 t の k 番目の値を x に変更する。
size(t):= 木 t のノード数を返す。
makeset():= 空木を返す。
*/
template< class Monoid, class OperatorMonoid = Monoid >
struct RandomizedBinarySearchTree {
	using F = function< Monoid(Monoid, Monoid) >;
	using G = function< Monoid(Monoid, OperatorMonoid, ll) >;
	using H = function< OperatorMonoid(OperatorMonoid, OperatorMonoid) >;

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

	struct Node {
		ll cnt;
		Monoid key, sum;
		OperatorMonoid lazy;
		Node *l, *r;
		Node() = default;
		Node(const Monoid &k, const OperatorMonoid &p) : cnt(1), key(k), sum(k), lazy(p), l(nullptr), r(nullptr) {}
	};

	vector< Node > pool;
	ll ptr;
	const F f;
	const G g;
	const H h;
	const Monoid M1;
	const OperatorMonoid OM0;

	RandomizedBinarySearchTree(ll sz, const F &f, const Monoid &M1) :
			pool(sz), ptr(0), f(f), g(G()), h(H()), M1(M1), OM0(OperatorMonoid()) {}

	RandomizedBinarySearchTree(ll sz, const F &f, const G &g, const H &h,
								 const Monoid &M1, const OperatorMonoid &OM0) :
			pool(sz), ptr(0), f(f), g(g), h(h), M1(M1), OM0(OM0) {}

	inline Node *alloc(const Monoid &key) { return &(pool[ptr++] = Node(key, OM0)); }

	virtual Node *clone(Node *t) { return t; }

	inline ll count(const Node *t) { return t ? t->cnt : 0; }

	inline Monoid sum(const Node *t) { return t ? t->sum : M1; }

	inline Node *update(Node *t) {
		t->cnt = count(t->l) + count(t->r) + 1;
		t->sum = f(f(sum(t->l), t->key), sum(t->r));
		return t;
	}

	Node *propagate(Node *t) {
		t = clone(t);
		if(t->lazy != OM0) {
			t->key = g(t->key, t->lazy, 1);
			if(t->l) {
				t->l = clone(t->l);
				t->l->lazy = h(t->l->lazy, t->lazy);
				t->l->sum = g(t->l->sum, t->lazy, count(t->l));
			}
			if(t->r) {
				t->r = clone(t->r);
				t->r->lazy = h(t->r->lazy, t->lazy);
				t->r->sum = g(t->r->sum, t->lazy, count(t->r));
			}
			t->lazy = OM0;
		}
		return update(t);
	}

	Node *merge(Node *l, Node *r) {
		if(!l || !r) return l ? l : r;
		if(xor128() % (l->cnt + r->cnt) < l->cnt) {
			l = propagate(l);
			l->r = merge(l->r, r);
			return update(l);
		} else {
			r = propagate(r);
			r->l = merge(l, r->l);
			return update(r);
		}
	}

	pair< Node *, Node * > split(Node *t, ll k) {
		if(!t) return {t, t};
		t = propagate(t);
		if(k <= count(t->l)) {
			auto s = split(t->l, k);
			t->l = s.second;
			return {s.first, update(t)};
		} else {
			auto s = split(t->r, k - count(t->l) - 1);
			t->r = s.first;
			return {update(t), s.second};
		}
	}

	Node *build(ll l, ll r, const vector< Monoid > &v) {
		if(l + 1 >= r) return alloc(v[l]);
		return merge(build(l, (l + r) >> 1, v), build((l + r) >> 1, r, v));
	}

	Node *build(const vector< Monoid > &v) {
		ptr = 0;
		return build(0, (ll) v.size(), v);
	}

	void dump(Node *r, typename vector< Monoid >::iterator &it) {
		if(!r) return;
		r = propagate(r);
		dump(r->l, it);
		*it = r->key;
		dump(r->r, ++it);
	}

	vector< Monoid > dump(Node *r) {
		vector< Monoid > v((size_t) count(r));
		auto it = begin(v);
		dump(r, it);
		return v;
	}

	string to_string(Node *r) {
		auto s = dump(r);
		string ret;
		for(ll i = 0; i < (ll)s.size(); i++) ret += std::to_string(s[i]) + ", ";
		return (ret);
	}

	void insert(Node *&t, ll k, const Monoid &v) {
		auto x = split(t, k);
		t = merge(merge(x.first, alloc(v)), x.second);
	}

	void erase(Node *&t, ll k) {
		auto x = split(t, k);
		t = merge(x.first, split(x.second, 1).second);
	}

	Monoid query(Node *&t, ll a, ll b) {
		auto x = split(t, a);
		auto y = split(x.second, b - a);
		auto ret = sum(y.first);
		t = merge(x.first, merge(y.first, y.second));
		return ret;
	}

	void set_propagate(Node *&t, ll a, ll b, const OperatorMonoid &p) {
		auto x = split(t, a);
		auto y = split(x.second, b - a);
		y.first->lazy = h(y.first->lazy, p);
		t = merge(x.first, merge(propagate(y.first), y.second));
	}

	void set_element(Node *&t, ll k, const Monoid &x) {
		t = propagate(t);
		if(k < count(t->l)) set_element(t->l, k, x);
		else if(k == count(t->l)) t->key = t->sum = x;
		else set_element(t->r, k - count(t->l) - 1, x);
		t = update(t);
	}

	ll size(Node *t) {
		return count(t);
	}

	bool empty(Node *t) {
		return !t;
	}

	Node *makeset() {
		return nullptr;
	}
};
//kth_element は k(0-indeed) 番目に小さい値を返す。
template< class T >
struct OrderedMultiSet : RandomizedBinarySearchTree< T > {
	using RBST = RandomizedBinarySearchTree< T >;
	using Node = typename RBST::Node;

	OrderedMultiSet(ll sz) : RBST(sz, [&](T x, T y) { return x; }, T()) {}

	T kth_element(Node *t, ll k) {
		if(k < RBST::count(t->l)) return kth_element(t->l, k);
		if(k == RBST::count(t->l)) return t->key;
		return kth_element(t->r, k - RBST::count(t->l) - 1);
	}

	virtual void insert_key(Node *&t, const T &x) {
		RBST::insert(t, lower_bound(t, x), x);
	}

	void erase_key(Node *&t, const T &x) {
		if(!count(t, x)) return;
		RBST::erase(t, lower_bound(t, x));
	}

	ll count(Node *t, const T &x) {
		return upper_bound(t, x) - lower_bound(t, x);
	}

	ll lower_bound(Node *t, const T &x) {
		if(!t) return 0;
		if(x <= t->key) return lower_bound(t->l, x);
		return lower_bound(t->r, x) + RBST::count(t->l) + 1;
	}

	ll upper_bound(Node *t, const T &x) {
		if(!t) return 0;
		if(x < t->key) return upper_bound(t->l, x);
		return upper_bound(t->r, x) + RBST::count(t->l) + 1;
	}
};
template< class T >
struct OrderedSet : OrderedMultiSet< T > {
	using SET = OrderedMultiSet< T >;
	using RBST = typename SET::RBST;
	using Node = typename RBST::Node;

	OrderedSet(ll sz) : OrderedMultiSet< T >(sz) {}

	void insert_key(Node *&t, const T &x) override {
		if(SET::count(t, x)) return;
		RBST::insert(t, SET::lower_bound(t, x), x);
	}
};

/*-----8<-----library-----8<-----*/

void solve() {
	ll N,Q;
	cin>>N>>Q;
	vector<ll> a(N);
	rep(i,N)cin>>a[i];

	//auto F = [](ll x, ll y){ return x+y; };
	//RandomizedBinarySearchTree<ll> rbst(N, F, 0);
	//using Node = RandomizedBinarySearchTree<ll>::Node;
	
	auto F = [](ll x, ll y){ return min(x,y); };
	auto G = [](ll x, ll y, ll len){ return min(x,y); };
	RandomizedBinarySearchTree<ll> rbst(N, F, G, F, INF, INF);
	using Node = RandomizedBinarySearchTree<ll>::Node;
	
	//OrderedMultiSet<ll> ms(N);
	//OrderedSet<ll> s(N);

	//空で初期化
	//Node* node = rbst.makeset();
	//配列で初期化
	Node* node = rbst.build(a);

	vector<ll> ans;
	rep(q,Q){
		ll x,y,z;
		cin>>x>>y>>z;
		if(x==0){
			pair<Node*,Node*> lm = rbst.split(node, y);
			pair<Node*,Node*> mm = rbst.split(lm.second, z-y);
			pair<Node*,Node*> mr = rbst.split(mm.second, 1);
			node=rbst.merge(lm.first, mr.first);
			node=rbst.merge(node, mm.first);
			node=rbst.merge(node, mr.second);
		}else if(x==1){
			ll t=rbst.query(node, y, z+1);
			ans.push_back(t);
		}else{
			rbst.set_element(node, y, z);
		}
		debug(rbst.to_string(node));
	}
	
	debug(rbst.to_string(node));
	rep(i,ans.size())p(ans[i]);
}

signed main() {
	solve();
	return 0;
}



Measured memory_bytes: 14856