#include <bits/stdc++.h>

using namespace std;

using int64 = long long;

// A lazy-propagation segment tree for range-update and range-get.
// T: The type of values. T needs to have a default constructor.
// Verified: AOJ-DSL2F
template<typename T>
class RURGSegmentTree {
 private:
  tuple<T, bool> MergeUpdates(tuple<T, bool> u1, tuple<T, bool> u2) {
    if (get<1>(u2)) return u2;
    return u1;
  }

  T ApplyUpdate(T v, tuple<T, bool> u) {
    if (get<1>(u)) return get<0>(u);
    return v;
  }

  const tuple<T, bool> EMPTY_UPDATE = make_tuple(T(), false);

  function<T(T, T)> merge_fn;
  T empty_value;
  int num_nodes;
  vector<T> nodes;
  vector<tuple<T, bool>> op_nodes;

  void DoLazyPropagation(int node_index) {
   if (get<1>(op_nodes[node_index])) {
      nodes[node_index] = ApplyUpdate(nodes[node_index], op_nodes[node_index]);
      if (node_index < num_nodes / 2) {
        op_nodes[2 * node_index] =
            MergeUpdates(op_nodes[2 * node_index], op_nodes[node_index]);
        op_nodes[2 * node_index + 1] =
            MergeUpdates(op_nodes[2 * node_index + 1], op_nodes[node_index]);
      }
      op_nodes[node_index] = EMPTY_UPDATE;
    }
  }

  void UpdateRangeInternal(
      int node_index, int node_l, int node_r, int l, int r, T v) {
    DoLazyPropagation(node_index);

    if (node_r <= l || r <= node_l) return;

    if (l <= node_l && node_r <= r) {
      op_nodes[node_index] = make_tuple(v, true);
      DoLazyPropagation(node_index);
      return;
    }
    int node_m = (node_l + node_r) / 2;
    UpdateRangeInternal(2 * node_index, node_l, node_m, l, r, v);
    UpdateRangeInternal(2 * node_index + 1, node_m, node_r, l, r, v);
    nodes[node_index] =
        merge_fn(nodes[2 * node_index], nodes[2 * node_index + 1]);
  }

  T GetRangeInternal(
      int node_index, int node_l, int node_r, int l, int r) {
    DoLazyPropagation(node_index);

    if (node_r <= l || r <= node_l) return empty_value;

    if (l <= node_l && node_r <= r) {
      return nodes[node_index];
    }

    int node_m = (node_l + node_r) / 2;
    return merge_fn(
        GetRangeInternal(2 * node_index, node_l, node_m, l, r),
        GetRangeInternal(2 * node_index + 1, node_m, node_r, l, r));
  }

 public:
  RURGSegmentTree(
      function<T(T, T)> merge_fn, T empty_value, vector<T> values) :
          merge_fn(merge_fn), empty_value(empty_value) {
    int n = values.size();
    num_nodes = 1;
    while (num_nodes < n) num_nodes *= 2;
    num_nodes *= 2;
    nodes = vector<T>(num_nodes, empty_value);
    op_nodes = vector<tuple<T, bool>>(num_nodes, EMPTY_UPDATE);
    for (int i = 0; i < values.size(); ++i) {
      nodes[num_nodes / 2 + i] = values[i];
    }
    for (int i = num_nodes / 2 - 1; i >= 1; i--) {
      nodes[i] = merge_fn(nodes[2 * i], nodes[2 * i + 1]);
    }
  }

  // Updates the range [l, r) to v.
  void UpdateRange(int l, int r, T v) {
    return UpdateRangeInternal(1, 0, num_nodes / 2, l, r, v);
  }

  T GetRange(int l, int r) {
    return GetRangeInternal(1, 0, num_nodes / 2, l, r);
  }
  
  T Get(int i) { return GetRange(i, i + 1); }
};

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

  int n, q;
  cin >> n >> q;
  vector<int> xs(n, INT_MAX);
  constexpr int INVALID_OP = -1;

  auto merge =
      function<int(int, int)>([] (int x, int y) { return min(x, y); });
  auto op_merge =
      function<int(int, int)>([] (int x, int y) { return y; });
  auto apply_op =
      function<int(int, int)>([] (int x, int op) { return op; });
  auto tree = RURGSegmentTree<int>(merge, INT_MAX, xs);

  for (int i = 0; i < q; i++) {
    int c;
    cin >> c;
    if (c == 0) {
      int s, t, v;
      cin >> s >> t >> v;
      tree.UpdateRange(s, t + 1, v);
      // tree.Dump();
    } else {
      int s, t;
      cin >> s >> t;
      cout << tree.GetRange(s, t + 1) << endl;
      // tree.Dump();
    }
  }
}


Measured memory_bytes: 6816