#include <algorithm>
#include <cstdlib>
#include <iostream>
#include <memory>
#include <numeric>
#include <vector>
using namespace std;

unsigned seed128[4];
void seed(unsigned s){
	for(int i=1; i<=4; i++)
		seed128[i-1] = s = 1812433253U * (s^(s>>30)) + i;
}

unsigned xor_shift() {
	unsigned t;
	t=(seed128[0]^(seed128[0]<<11));
	seed128[0]=seed128[1];
	seed128[1]=seed128[2];
	seed128[2]=seed128[3];
	return seed128[3]=(seed128[3]^(seed128[3]>>19))^(t^(t>>8));
}

// Randomized Binary Search Tree
// infix, 0-based

typedef int type;
constexpr int INF = (1 << 29);
constexpr type INIT = INF;
constexpr int MAX_N = 200000;

// update function
inline type func(type a, type b) {
	return min(a, b);
}

struct RBST {
	type value, res;
	int size;
	RBST *left, *right;

	RBST(int v = 0):value(v), res(v), size(1), left(nullptr), right(nullptr) {}

	void update() {
		size = 1;
		res = value;

		if(left) {
			size += left->size;
			res = func(res, left->res);
		}

		if(right) {
			size += right->size;
			res = func(res, right->res);
		}
	}
};

RBST nodes[MAX_N];
vector<int> pool(MAX_N);

inline int node_size(const RBST *x) {
	return x ? x->size : 0;
}

inline RBST *make_node(type value = 0) {
	const int v = pool.back();
	nodes[v].value = nodes[v].res = value;
	nodes[v].size = 1;
	nodes[v].left = nodes[v].right = nullptr;
	pool.pop_back();
	return nodes + v;
}

inline void free_node(RBST *x) {
	pool.emplace_back(x - nodes);
}

RBST *merge(RBST *x, RBST *y) {
	if(!x) return y;
	if(!y) return x;

	if(xor_shift() % (x->size + y->size) < x->size) {
		x->right = merge(x->right, y);
		x->update();
		return x;
	}
	else {
		y->left = merge(x, y->left);
		y->update();
		return y;
	}
}

typedef pair<RBST *, RBST *> P;

P split(RBST *x, int num) {
	if(!x) return P(nullptr, nullptr);

	const int l_size = node_size(x->left);
	if(l_size < num) {
		const P tmp = split(x->right, num - l_size - 1);
		x->right = tmp.first;
		x->update();
		return P(x, tmp.second);
	}
	else {
		const P tmp = split(x->left, num);
		x->left = tmp.second;
		x->update();
		return P(tmp.first, x);
	}
}

RBST *insert(RBST *x, int pos, type value) {
	RBST *new_node = make_node(value);
	const P tmp = split(x, pos);
	return merge(merge(tmp.first, new_node), tmp.second);
}

RBST *erase(RBST *x, int pos) {
	P tmp = split(x, pos);
	RBST *left = tmp.first;
	tmp = split(tmp.second, 1);
	free_node(tmp.first);
	return merge(left, tmp.second);
}

// [l, r)
type query(RBST *x, int l, int r) {
	if(!x) return INIT;
	if(r <= 0 || x->size <= l) return INIT;
	if(l <= 0 && x->size <= r) return x->res;

	const int l_size = node_size(x->left);
	const type res = (l <= l_size && l_size < r ? x->value : INIT);
	const type vl = query(x->left, l, r);
	const type vr = query(x->right, l - l_size - 1, r - l_size - 1);
	return func(res, func(vl, vr));
}

enum { SHIFT, QUERY, UPDATE };

int main() {
	cin.tie(nullptr);
	ios::sync_with_stdio(false);

	iota(begin(pool), end(pool), 0);
	seed(time(NULL));

	int n, q;
	cin >> n >> q;

	RBST *root = nullptr;
	for(int i = 0; i < n; ++i) {
		int a;
		cin >> a;
		root = insert(root, i, a);
	}

	while(q--) {
		int x, y, z;
		cin >> x >> y >> z;
		switch(x) {
		case SHIFT:
		{
			const auto value = query(root, z, z + 1);
			root = erase(root, z);
			root = insert(root, y, value);
			break;
		}
		case QUERY:
		{
			cout << query(root, y, z + 1) << '\n';
			break;
		}
		case UPDATE:
		{
			root = erase(root, y);
			root = insert(root, y, z);
			break;
		}
		}
	}

	return EXIT_SUCCESS;
}

Measured memory_bytes: 7996