#include <cassert>// c
#include <iostream>// io
#include <iomanip>
#include <fstream>
#include <sstream>
#include <vector>// container
#include <map>
#include <set>
#include <queue>
#include <bitset>
#include <stack>
#include <algorithm>// other
#include <complex>
#include <numeric>
#include <functional>
#include <random>
#include <regex>
using namespace std;

typedef long long ll;
typedef unsigned long long ull;

#define ALL(c) (begin(c)),(end(c))
#define REP(i,n) FOR(i,0,n)
#define REPr(i,n) FORr(i,0,n)
#define FOR(i,l,r) for(int i=(int)(l);i<(int)(r);++i)
#define FORr(i,l,r) for(int i=(int)(r)-1;i>=(int)(l);--i)
#define EACH(it,o) for(auto it = (o).begin(); it != (o).end(); ++it)
#define IN(l,v,r) ((l)<=(v) && (v)<(r))
#define UNIQUE(v) v.erase(unique(ALL(v)),v.end())
//debug
#define DUMP(x)  cerr << #x << " = " << (x)
#define LINE()    cerr<< " (L" << __LINE__ << ")"

class range {
private:
	struct Iter{
		int v;
		int operator*(){return v;}
		bool operator!=(Iter& itr) {return v < itr.v;}
		void operator++() {++v;}
	};
	Iter i, n;
public:
	range(int n) : i({0}), n({n}) {}
	range(int i, int n) : i({i}), n({n}) {}
	Iter& begin() {return i;}
	Iter& end() {return n;}
};

//output
template<typename T> ostream& operator << (ostream& os, const vector<T>& as){REP(i,as.size()){if(i!=0)os<<" "; os<<as[i];}return os;}
template<typename T> ostream& operator << (ostream& os, const vector<vector<T>>& as){REP(i,as.size()){if(i!=0)os<<endl; os<<as[i];}return os;}
template<typename T> ostream& operator << (ostream& os, const set<T>& ss){for(auto a:ss){if(a!=ss.begin())os<<" "; os<<a;}return os;}
template<typename T1,typename T2> ostream& operator << (ostream& os, const pair<T1,T2>& p){os<<p.first<<" "<<p.second;return os;}
template<typename K,typename V> ostream& operator << (ostream& os, const map<K,V>& m){bool isF=true;for(auto& p:m){if(!isF)os<<endl;os<<p;isF=false;}return os;}
template<typename T1> ostream& operator << (ostream& os, const tuple<T1>& t){os << get<0>(t);return os;}
template<typename T1,typename T2> ostream& operator << (ostream& os, const tuple<T1,T2>& t){os << get<0>(t)<<" "<<get<1>(t);return os;}
template<typename T1,typename T2,typename T3> ostream& operator << (ostream& os, const tuple<T1,T2,T3>& t){os << get<0>(t)<<" "<<get<1>(t)<<" "<<get<2>(t);return os;}
template<typename T1,typename T2,typename T3,typename T4> ostream& operator << (ostream& os, const tuple<T1,T2,T3,T4>& t){os << get<0>(t)<<" "<<get<1>(t)<<" "<<get<2>(t)<<" "<<get<3>(t);return os;}
template<typename T1,typename T2,typename T3,typename T4,typename T5> ostream& operator << (ostream& os, const tuple<T1,T2,T3,T4,T5>& t){os << get<0>(t)<<" "<<get<1>(t)<<" "<<get<2>(t)<<" "<<get<3>(t)<<" "<<get<4>(t);return os;}
template<typename T1,typename T2,typename T3,typename T4,typename T5,typename T6> ostream& operator << (ostream& os, const tuple<T1,T2,T3,T4,T5,T6>& t){os << get<0>(t)<<" "<<get<1>(t)<<" "<<get<2>(t)<<" "<<get<3>(t)<<" "<<get<4>(t)<<" "<<get<5>(t);return os;}
template<typename T1,typename T2,typename T3,typename T4,typename T5,typename T6,typename T7> ostream& operator << (ostream& os, const tuple<T1,T2,T3,T4,T5,T6,T7>& t){os << get<0>(t)<<" "<<get<1>(t)<<" "<<get<2>(t)<<" "<<get<3>(t)<<" "<<get<4>(t)<<" "<<get<5>(t)<<" "<<get<6>(t);return os;}

//input
char tmp[1000];
#define nextInt(n) scanf("%d",&n)
#define nextLong(n) scanf("%lld",&n) //I64d
#define nextDouble(n) scanf("%lf",&n) 
#define nextChar(n) scanf("%c",&n)
#define nextString(n) scanf("%s",tmp);n=tmp

// values
template<typename T> T INF(){assert(false);};
template<> int INF<int>(){return 1<<28;};
template<> ll INF<ll>(){return 1LL<<58;};
template<> double INF<double>(){return 1e16;};
template<> long double INF<long double>(){return 1e16;};

template<class T> T EPS(){assert(false);};
template<> int EPS<int>(){return 1;};
template<> ll EPS<ll>(){return 1LL;};
template<> double EPS<double>(){return 1e-8;};
template<> long double EPS<long double>(){return 1e-8;};

template<typename T,typename U> T pmod(T v,U M){return (v%M+M)%M;}

//insert - erase
template<typename T> struct TreapRMQ{
public:
	struct Node{
		T v;int p;// 値,優先度
		int sz,mv;//部分木のサイズ,最小値
		Node *l,*r;// l,r
		Node(T v,int p):v(v),p(p),sz(1),mv(v){
			l=r=NULL;
		}
	};

	unsigned size(){return size(root);}
	unsigned size(Node* t){return !t?0:t->sz;}
	T mv(Node* t){return !t?INF<T>():t->mv;}

	Node* root;
	TreapRMQ():root(NULL){};

	Node* update(Node* t){
		t->sz=size(t->l)+size(t->r)+1;
		t->mv=min(min(mv(t->l),mv(t->r)),t->v);
		return t;
	}

	Node* merge(Node* l,Node* r){
		if(!l || !r)return l?l:r;
		
		if(l->p > r->p){
			l->r = merge(l->r,r);
			return update(l);
		}else{
			r->l = merge(l,r->l);
			return update(r);
		}
	}

	pair<Node*,Node*> split(Node* t,unsigned k){//[0,k) [k,n)
		if(!t)return make_pair((Node*)NULL,(Node*)NULL);
		if(k<=size(t->l)){
			auto s=split(t->l,k);
			t->l=s.second;
			return make_pair(s.first,update(t));
		}else{
			auto s=split(t->r,k - (size(t->l)+1));
			t->r=s.first;
			return make_pair(update(t),s.second);
		}
	}

	Node* insert(unsigned k,T v){return root=insert(root,k,v,rand());}
	Node* insert(Node* t,unsigned k,T v,int p){
		pair<Node*,Node*> s=split(t,k);
		t=merge(s.first,new Node(v,p));
		t=merge(t,s.second);
		return update(t);
	}

	Node* erase(unsigned k){return root=erase(root,k);}
	Node* erase(Node* t,unsigned k){
		pair<Node*,Node*> sr=split(t,k+1),sl=split(sr.first,k);	
		t = merge(sl.first,sr.second);
		return update(t);
	}

	Node* find(unsigned k){
		Node* v=root;
		while(v){
			unsigned cl=size(v->l);
			if(k==cl)return v;
			else if(k<cl) v=v->l;
			else {
				v=v->r;
				k -= cl+1;
			}
		}
		return v;
	}

  	// Node *find(Node *t, int k){
   //      int c = count(t->lch);
   //      if(k <  c) return find(t->lch, k);
   //      if(k == c) return t;
   //      return find(t->rch, k - c - 1);
   //  }
   //  Node *find(int k){ return find(root, k); }
    

	Node* shift(unsigned l,unsigned r){
		pair<Node*,Node*> s1,s2,s3;
		s3=split(root,r);s2=split(s3.first,r-1);s1=split(s2.first,l);
		return root=update(merge(merge(merge(s1.first,s2.second),s1.second),s3.second));
	}

	T query(int l,int r){return query(l,r,root);}

	T query(int l,int r,Node* t){
		int sz=size(t);
		if(sz<=l || r<=0)return INF<T>();
		if(l<=0 && sz<=r)return mv(t);

		int c=size(t->l);
		T res=min(query(l,r,t->l),query(l-c-1,r-c-1,t->r));
		if(l<=c && c < r)res=min(res,t->v);
		return res;
	}
};

class Main{
	public:
	void run(){
		int n,q;
		TreapRMQ<int> t;

		cin >> n >> q;
		REP(i,n){
			int a;cin >> a;t.insert(i,a);
		}

		while(q--){
			int x,y,z;cin >> x >> y >> z;
			if(x==2){
				t.erase(y);t.insert(y,z);
			}else if(x==1){

				cout << t.query(y,z+1)<<endl;
			}else{
				t.shift(y,z+1);
			}
		}
	}
};

int main(){
	cout <<fixed<<setprecision(20);
	cin.tie(0);
	ios::sync_with_stdio(false);
	Main().run();
	return 0;
}

Measured memory_bytes: 13720