For a permutation , call a subarray an interval if its values form a contiguous range, i.e. . The permutation tree (also called the cut/join tree) encodes all intervals in nodes. Problems: count intervals (CF 526F), count intervals inside a query range (CF 997E), shortest interval containing given positions (gym 101620 I), count permutations with no nontrivial interval (simple permutations).

Counting intervals without the tree

For a fixed right end (exclusive), let . Then , with equality iff is an interval. Keep in a segment tree with range add and “min + count of min”:

  • moving forward subtracts 1 from every , (the length term) and sets ;
  • the max term changes on a contiguous block of per popped element of a monotonic max-stack; do a range add of the difference. Same for the min-stack.

The number of intervals ending at is the number of zeros among , which is the count of the minimum when the minimum is 0. Total .

// seg: range add, query (min, count of min) over [0, n)
ll countIntervals(const vector<int>& p) {
    int n = sz(p); ll ans = 0; vector<int> mx, mn;              // stacks of indices
    for (int r = 0; r < n; r++) {
        while (sz(mx) && p[mx.back()] < p[r]) { int j = mx.back(); mx.pop_back(); seg.add(sz(mx) ? mx.back() + 1 : 0, j + 1, p[r] - p[j]); }
        mx.push_back(r);
        while (sz(mn) && p[mn.back()] > p[r]) { int j = mn.back(); mn.pop_back(); seg.add(sz(mn) ? mn.back() + 1 : 0, j + 1, p[j] - p[r]); }
        mn.push_back(r);
        if (r) seg.add(0, r, -1);                                // the length term grew by one
        auto [m, c] = seg.query(0, r + 1);
        ans += m == 0 ? c : 0;
    }
    return ans;
}

For “number of intervals inside ” (CF 997E), add a lazy tag “add the current zero-count to a per-node historic counter” and apply it to the whole prefix after every step; then answer queries with right end by a range query of historic counts over .

Structure

Two intervals overlap if they intersect and neither contains the other.

An interval is strong if no interval overlaps it. Strong intervals form a laminar family (any two are nested or disjoint), so they form a tree: the root is , the leaves are single positions, and the children of a node are its maximal strong proper subintervals, which partition it. Read the children’s value blocks in order; their relative order (the quotient permutation) is either

  • monotone (a join node, increasing or decreasing): every union of consecutive children is an interval; or
  • simple (a cut/prime node): no union of consecutive children is an interval. A simple permutation has (none exist for ).

Every interval is either a node or a union of consecutive children of a join node. So the number of intervals is

construction

Sweep with the -segment tree above and a stack of nodes whose position ranges tile . When a new leaf arrives, repeatedly:

  1. if the top of the stack is a join node whose last child is adjacent in value to the current node in the same direction, append the current node as its child;
  2. else if the top and the current node are adjacent in value, create a new join node with these two children;
  3. else if the segment tree says some to the left gives an interval ending here (min of over is 0), pop nodes until their union is an interval and make them children of a new cut node;
  4. otherwise stop and push the current node.
struct PermutationTree {                             // p: permutation of 0..n-1
    int n, root = -1, cnt = 0, LOG = 1;
    vector<vector<int>> ch;                          // children ordered by position; leaves are nodes 0..n-1
    vector<int> type, lo, hi, vlo, vhi, par;         // type 0 leaf/cut, 1 increasing join, 2 decreasing join; [lo, hi), [vlo, vhi)
    vector<vector<int>> up; vector<int> mn, lz;
    void app(int p, int v) { mn[p] += v, lz[p] += v; }
    void push(int p) { if (lz[p]) app(2 * p, lz[p]), app(2 * p + 1, lz[p]), lz[p] = 0; }
    void update(int p, int b, int e, int l, int r, int v) {   // closed ranges, 1-indexed
        if (r < b || e < l || l > r) return;
        if (l <= b && e <= r) return app(p, v);
        push(p); int m = (b + e) / 2;
        update(2 * p, b, m, l, r, v); update(2 * p + 1, m + 1, e, l, r, v);
        mn[p] = min(mn[2 * p], mn[2 * p + 1]);
    }
    int query(int p, int b, int e, int l, int r) {
        if (r < b || e < l || l > r) return INT_MAX;
        if (l <= b && e <= r) return mn[p];
        push(p); int m = (b + e) / 2;
        return min(query(2 * p, b, m, l, r), query(2 * p + 1, m + 1, e, l, r));
    }
    PermutationTree(const vector<int>& perm) : n(sz(perm)) {
        if (!n) return;
        int N = 2 * n + 2; vector<int> p(n + 1);
        for (int i = 0; i < n; i++) p[i + 1] = perm[i] + 1;
        vector<vector<int>> g(N); vector<int> ty(N), pa(N, -1), rl(N), rr(N), sl(N), sr(N);
        mn.assign(4 * n + 4, 0), lz.assign(4 * n + 4, 0);
        auto addEdge = [&](int u, int v) { pa[v] = u, g[u].push_back(v); };
        auto adj = [&](int i, int j) { return rr[i] == rl[j] - 1; };
        auto len = [&](int i) { return rr[i] - rl[i] + 1; };
        int id = n + 1; vector<int> mx = {0}, mi = {0}, st;
        for (int i = 1; i <= n; i++) {
            while (mx.back() && p[mx.back()] < p[i]) { int r = mx.back(); mx.pop_back(); update(1, 1, n, mx.back() + 1, r, p[i] - p[r]); }
            mx.push_back(i);
            while (mi.back() && p[mi.back()] > p[i]) { int r = mi.back(); mi.pop_back(); update(1, 1, n, mi.back() + 1, r, p[r] - p[i]); }
            mi.push_back(i);
            rl[i] = rr[i] = p[i]; sl[i] = sr[i] = i;
            int cur = i;
            while (true) {
                if (sz(st) && (adj(st.back(), cur) || adj(cur, st.back()))) {
                    int b = st.back();
                    if ((adj(b, cur) && ty[b] == 1) || (adj(cur, b) && ty[b] == 2)) {
                        addEdge(b, cur);
                        rl[b] = min(rl[b], rl[cur]), rr[b] = max(rr[b], rr[cur]), sl[b] = min(sl[b], sl[cur]), sr[b] = max(sr[b], sr[cur]);
                        cur = b; st.pop_back();
                    } else {
                        ty[id] = adj(b, cur) ? 1 : 2; addEdge(id, b); addEdge(id, cur);
                        rl[id] = min(rl[b], rl[cur]), rr[id] = max(rr[b], rr[cur]), sl[id] = min(sl[b], sl[cur]), sr[id] = max(sr[b], sr[cur]);
                        st.pop_back(); cur = id++;
                    }
                } else if (query(1, 1, n, 1, i - len(cur)) == 0) {
                    int L = len(cur), a = rl[cur], b = rr[cur], s1 = sl[cur], s2 = sr[cur];
                    addEdge(id, cur);
                    do {
                        int t = st.back(); L += len(t);
                        a = min(a, rl[t]), b = max(b, rr[t]), s1 = min(s1, sl[t]), s2 = max(s2, sr[t]);
                        addEdge(id, t); st.pop_back();
                    } while (b - a + 1 != L);
                    reverse(all(g[id]));
                    rl[id] = a, rr[id] = b, sl[id] = s1, sr[id] = s2; cur = id++;
                } else break;
            }
            st.push_back(cur);
            update(1, 1, n, 1, i, -1);
        }
        cnt = id - 1;
        ch.assign(cnt, {}), type.assign(cnt, 0), lo.assign(cnt, 0), hi.assign(cnt, 0), vlo.assign(cnt, 0), vhi.assign(cnt, 0), par.assign(cnt, -1);
        for (int u = 1; u <= cnt; u++) {
            int x = u - 1;
            for (int v : g[u]) ch[x].push_back(v - 1);
            type[x] = ty[u]; lo[x] = sl[u] - 1, hi[x] = sr[u]; vlo[x] = rl[u] - 1, vhi[x] = rr[u];
            par[x] = pa[u] == -1 ? -1 : pa[u] - 1;
            if (pa[u] == -1) root = x;
        }
        while ((1 << LOG) < cnt) LOG++;
        up.assign(LOG, vector<int>(cnt));
        for (int u = 0; u < cnt; u++) up[0][u] = par[u] == -1 ? u : par[u];
        for (int k = 1; k < LOG; k++) for (int u = 0; u < cnt; u++) up[k][u] = up[k - 1][up[k - 1][u]];
    }
    pair<int, int> minimalInterval(int l, int r) const {     // shortest interval containing positions [l, r)
        if (r - l == 1) return {l, r};
        int u = l;
        for (int k = LOG - 1; k >= 0; k--) if (hi[up[k][u]] < r) u = up[k][u];
        u = par[u];
        if (type[u] == 0) return {lo[u], hi[u]};
        const auto& c = ch[u];
        int a = *partition_point(all(c), [&](int v) { return hi[v] <= l; });
        int b = *(partition_point(all(c), [&](int v) { return lo[v] < r; }) - 1);
        return {lo[a], hi[b]};
    }
    ll countIntervals() const {
        ll t = 0;
        for (int u = 0; u < cnt; u++) { t++; if (type[u]) { ll k = sz(ch[u]); t += k * (k - 1) / 2 - 1; } }
        return t;
    }
};

Uses

  • Minimal interval containing positions : lift from leaf to the deepest ancestor not containing , take its parent ; if is a cut node the answer is , otherwise the union of ‘s children from the one containing to the one containing . .
  • Counting simple permutations / permutations by interval structure: decompose by the root type; the generating function of simple permutations satisfies a functional equation with the one of all permutations.
  • “Sort by reversing intervals” and “split into maximum number of interval pieces”: DP over the tree; a join node’s children can be grouped freely.
  • Interval-based DP on permutations (“number of subarrays that are intervals and satisfy X”): iterate over nodes, handling join nodes’ consecutive unions in or .