Advanced Interval Data Structures for Algorithmic Challenges

Plane Closest Pair

A standard approach utilizes divide and conquer strategies. Sort all points by their x-corodinate recursively split the set into two halves. After solving subproblems, examine points near the dividing line that could potentially form a shorter pair then the current minimum found.

const int MAX_PTS = 250005;
struct Point {
    long long x, y;
};

int total_points;
Point pts[MAX_PTS];
Point buffer[250005];

bool compareX(const Point& l, const Point& r) {
    if (l.x == r.x) return l.y < r.y;
    return l.x < r.x;
}

long long distSq(int i, int j) {
    return (pts[i].x - pts[j].x) * (pts[i].x - pts[j].x) + 
           (pts[i].y - pts[j].y) * (pts[i].y - pts[j].y);
}

bool compareY(int a_idx, int b_idx) {
    return pts[a_idx].y < pts[b_idx].y;
}

double solveRec(int left, int right) {
    double minDist = 1e9;
    if (left == right) return minDist;
    if (right == left + 1) {
        return sqrt((double)distSq(left, right));
    }
    int mid = (left + right) >> 1;
    double dLeft = solveRec(left, mid);
    double dRight = solveRec(mid + 1, right);
    double d = std::min(dLeft, dRight);
    
    int count = 0;
    for (int i = left; i <= right; ++i) {
        if (std::abs(pts[mid].x - pts[i].x) < d) {
            buffer[count++] = pts[i];
        }
    }
    
    std::sort(buffer, buffer + count, [](Point a, Point b) { return a.y < b.y; });
    
    for (int i = 0; i < count; ++i) {
        for (int j = i + 1; j < count && (buffer[j].y - buffer[i].y) < d; ++j) {
            double curDist = sqrt((double)distSq(
                std::distance(pts, pts + i), 
                std::distance(pts, pts + j)
            )); // Simplified index mapping for readability in snippet
        } 
    }
    
    // Correct logic restoration:
    for (int i = 0; i < count; ++i) {
        for (int j = i + 1; j < count && (buffer[j].y - buffer[i].y) < d; ++j) {
             double d3 = sqrt((double)(distSq(std::lower_bound(pts+left, pts+count, buffer[i], compareY) - pts, 
                                               std::lower_bound(pts+left, pts+count, buffer[j], compareY) - pts)));
             if (d > d3) d = d3;
        }
    }
    return d;
}
// Note: Full implementation assumes proper indexing management within recursive calls

Dynamic Line Management

This scenario maps well to Li Chao Segment Tree techniques for maximizing function values.

const int MAX_Q = 100005;
struct Function {
    long double k, b;
};

Function lines[MAX_Q];
int lineCount = 0;
int treeNodes[MAX_Q << 2];

inline long double evaluate(int id, long long x) {
    return lines[id].k * (x - 1) + lines[id].b;
}

const long double EPS = 1e-9;
inline int compareVal(long double a, long double b) {
    if (a - b > EPS) return 1;
    if (b - a > EPS) return -1;
    return 0;
}

void updateNode(int node, int L, int R, int newId) {
    int &currId = treeNodes[node];
    int mid = (L + R) >> 1;
    bool currBetter = compareVal(evaluate(newId, mid), evaluate(currId, mid)) >= 0 || 
                     (!compareVal(evaluate(newId, mid), evaluate(currId, mid)) && newId < currId);
    
    if (currBetter) std::swap(newId, currId);
    
    int leftDiff = compareVal(evaluate(newId, L), evaluate(currId, L));
    int rightDiff = compareVal(evaluate(newId, R), evaluate(currId, R));
    
    if (leftDiff > 0 || (!leftDiff && newId < currId)) updateNode(2 * node, L, mid, newId);
    if (rightDiff > 0 || (!rightDiff && newId < currId)) updateNode(2 * node + 1, mid + 1, R, newId);
}

void insertRange(int node, int L, int R, int ql, int qr, int id) {
    if (ql <= L && R <= qr) {
        updateNode(node, L, R, id);
        return;
    }
    int mid = (L + R) >> 1;
    if (ql <= mid) insertRange(2 * node, L, mid, ql, qr, id);
    if (qr > mid) insertRange(2 * node + 1, mid + 1, R, ql, qr, id);
}

long double queryMax(int node, int L, int R, int x) {
    if (L == R) return evaluate(treeNodes[node], x);
    int mid = (L + R) >> 1;
    long double res = evaluate(treeNodes[node], x);
    if (x <= mid) res = std::max(res, queryMax(2 * node, L, mid, x));
    else res = std::max(res, queryMax(2 * node + 1, mid + 1, R, x));
    return res;
}

Range MEX Queries

While Mo's Algorithm suggests itself, finding the Minimum Excluded value efficiently requires optimization beyond naive iteration.

Two primary methods exist:

  1. Construct a Segment Tree over the value domain and perform binary search.
  2. Implement square root decomposition on the value range.

The following solution uses value blocking for complexity reduction.

const int MAX_VAL = 200005;
const int BLOCK_SIZE = 450;
int n, m;
int arr[MAX_VAL];
int pos[MAX_VAL];
int ans[MAX_VAL];
int cnt[MAX_VAL];
int blockCnt[MAX_VAL / BLOCK_SIZE + 5];

struct Query {
    int l, r, id;
} queries[MAX_VAL];

bool cmpQuery(Query a, Query b) {
    if (pos[a.l] != pos[b.l]) return pos[a.l] < pos[b.l];
    if (pos[a.l] & 1) return a.r > b.r;
    return a.r < b.r;
}

void addValue(int x) {
    if (!cnt[x]) blockCnt[x / BLOCK_SIZE]++;
    cnt[x]++;
}

void removeValue(int x) {
    cnt[x]--;
    if (!cnt[x]) blockCnt[x / BLOCK_SIZE]--;
}

void findMex(int qIdx) {
    for (int i = 0; i * BLOCK_SIZE < MAX_VAL; i++) {
        if (blockCnt[i] < BLOCK_SIZE) {
            for (int v = i * BLOCK_SIZE; v < (i + 1) * BLOCK_SIZE; v++) {
                if (!cnt[v]) {
                    ans[queries[qIdx].id] = v;
                    return;
                }
            }
        }
    }
}

void mainProcess() {
    // Input reading and sorting omitted for brevity
    // Initialize Mo's algorithm pointers
    int L = 1, R = 0;
    // Process queries...
}

Skyline Visibility Count

The problem simplifies to calculating the maximum visible slope sequence length using a Segment Tree. Each node maintains the local maximum height and the count of buildings visible from the start of its interval.

Merging intervals involves checking visibility from the right child against the left child's peak.

const int N = 100005;
int ans[N << 2];
double maxHeight[N << 2];

int countVisible(int node, int L, int R, double minHeight) {
    if (maxHeight[node] <= minHeight) return 0;
    if (L == R) return maxHeight[node] > minHeight ? 1 : 0;
    
    int mid = (L + R) >> 1;
    if (maxHeight[2 * node] <= minHeight) {
        return countVisible(2 * node + 1, mid + 1, R, minHeight);
    }
    return countVisible(2 * node, L, mid, minHeight) + ans[node] - ans[2 * node];
}

void update(int node, int L, int R, int pos, double slope) {
    if (L == R) {
        ans[node] = 1;
        maxHeight[node] = slope;
        return;
    }
    int mid = (L + R) >> 1;
    if (pos <= mid) update(2 * node, L, mid, pos, slope);
    else update(2 * node + 1, mid + 1, R, pos, slope);
    
    maxHeight[node] = std::max(maxHeight[2 * node], maxHeight[2 * node + 1]);
    ans[node] = ans[2 * node] + countVisible(2 * node + 1, mid + 1, R, maxHeight[2 * node]);
}

Historical Maximum Tracking

Tracking historical maximums requires careful lazy propagation handling. A naive approach fails because updates affect future states unpredictably.

The state must track addition operations and assignment operations separately, propagating prefixes of modifications correctly.

struct Node {
    int l, r;
    int currentAns;
    int historicalMax;
    int addVal;
    int assignVal;
    int addPrefixMax;
    int assignPrefixMax;
    bool isAssignSet;
};

Node tree[N << 2];

void pushUp(int k) {
    tree[k].currentAns = std::max(tree[2*k].currentAns, tree[2*k+1].currentAns);
    tree[k].historicalMax = std::max(tree[2*k].historicalMax, tree[2*k+1].historicalMax);
}

void applyAdd(int k, int val, int maxV) {
    if (tree[k].isAssignSet) {
        tree[k].assignPrefixMax = std::max(tree[k].assignPrefixMax, tree[k].assignVal + maxV);
        tree[k].historicalMax = std::max(tree[k].historicalMax, tree[k].currentAns + maxV);
        tree[k].currentAns += val;
        tree[k].assignVal += val;
    } else {
        tree[k].addPrefixMax = std::max(tree[k].addPrefixMax, tree[k].addVal + maxV);
        tree[k].historicalMax = std::max(tree[k].historicalMax, tree[k].currentAns + maxV);
        tree[k].currentAns += val;
        tree[k].addVal += val;
    }
}

void applyAssign(int k, int val, int maxV) {
    tree[k].isAssignSet = true;
    tree[k].currentAns = tree[k].assignVal = val;
    tree[k].historicalMax = std::max(tree[k].historicalMax, maxV);
    tree[k].assignPrefixMax = maxV;
    tree[k].addVal = tree[k].addPrefixMax = 0;
}

void pushDown(int k) {
    applyAdd(2*k, tree[k].addVal, tree[k].addPrefixMax);
    applyAdd(2*k+1, tree[k].addVal, tree[k].addPrefixMax);
    tree[k].addVal = tree[k].addPrefixMax = 0;
    
    if (tree[k].isAssignSet) {
        applyAssign(2*k, tree[k].assignVal, tree[k].assignPrefixMax);
        applyAssign(2*k+1, tree[k].assignVal, tree[k].assignPrefixMax);
        tree[k].isAssignSet = false;
        tree[k].assignVal = tree[k].assignPrefixMax = 0;
    }
}

Matrix Transformation Queries

Matrix problems involving ranges often leverage segment trees combined with matrix multiplication properties (associativity).

This setup supports Fibonacci-like recurrence relations where transitions are modified per range.

const int MOD = 1000000007;
struct Mat {
    int data[3][3];
    Mat() { memset(data, 0, sizeof(data)); }
    static Mat identity() {
        Mat res; res.data[1][1]=1; res.data[2][2]=1; return res;
    }
};

Mat operator+(Mat a, Mat b) {
    Mat c; c.identity();
    for(int i=1;i<=2;i++) for(int j=1;j<=2;j++) c.data[i][j] = (a.data[i][j]+b.data[i][j])%MOD;
    return c;
}

Mat operator*(Mat a, Mat b) {
    Mat c; 
    for(int i=1;i<=2;i++) for(int k=1;k<=2;k++) for(int j=1;j<=2;j++) 
        c.data[i][j] = (c.data[i][j] + 1LL*a.data[i][k]*b.data[k][j])%MOD;
    return c;
}

Mat matPow(Mat a, int p) {
    Mat res = Mat::identity();
    while(p){
        if(p&1) res = res * a;
        a = a * a;
        p >>= 1;
    }
    return res;
}

struct SegTree {
    struct Node { int l, r; Mat sum; Mat tag; } tr[N<<2];
    void pushUp(int k) { tr[k].sum = tr[2*k].sum + tr[2*k+1].sum; }
    
    void build(int k, int l, int r, int* arr, Mat base) {
        tr[k].l = l; tr[k].r = r; tr[k].tag = Mat::identity();
        if(l==r) {
            tr[k].sum = Mat::identity() * matPow(base, arr[l]-1);
            return;
        }
        int mid = (l+r)>>1;
        build(2*k, l, mid, arr, base);
        build(2*k+1, mid+1, r, arr, base);
        pushUp(k);
    }
    
    void pushDown(int k) {
        if(tr[k].tag.data[1][1]!=1) return; // Check empty/tag valid
        tr[2*k].sum = tr[2*k].sum * tr[k].tag;
        tr[2*k].tag = tr[2*k].tag * tr[k].tag;
        tr[2*k+1].sum = tr[2*k+1].sum * tr[k].tag;
        tr[2*k+1].tag = tr[2*k+1].tag * tr[k].tag;
        tr[k].tag = Mat::identity();
    }
    
    void update(int k, int l, int r, Mat val) {
        if(tr[k].l>=l && tr[k].r<=r) {
            tr[k].tag = tr[k].tag * val;
            tr[k].sum = tr[k].sum * val;
            return;
        }
        pushDown(k);
        int mid=(tr[k].l+tr[k].r)>>1;
        if(l<=mid) update(2*k, l, r, val);
        if(r>mid) update(2*k+1, l, r, val);
        pushUp(k);
    }
    
    Mat query(int k, int l, int r) {
        if(tr[k].l>=l && tr[k].r<=r) return tr[k].sum;
        pushDown(k);
        int mid=(tr[k].l+tr[k].r)>>1;
        Mat res = Mat::identity();
        if(l<=mid) res = res + query(2*k, l, r);
        if(r>mid) res = res + query(2*k+1, l, r);
        return res;
    }
} T;

Tags: Competitive Programming Data Structures segment tree Divide and Conquer algorithms

Posted on Sat, 12 Sep 2026 16:24:10 +0000 by Kyori