Competitive Programming Contest Solutions: Segment Trees and Combinatorial Optimization

Problem 1: Maximum Goals and Assists

Problem Overview

Given two arrays representing goals and assists for multiple players, process queries that ask for the maximum total balls needed under different matching scenarios.

Key Observations

The problem essentially asks for the maximum value among three distinct scenarios:

Scenario 1: Each assist can be matched to a goal. No additional balls are required beyond sum of goals.

Scenario 2: Every goal matches an assist, but remaining assists exist. Player n+1 must score enough to cover unmatched assists.

Scenario 3: A single player's assists cover all opponents' goals AND that player's goals cover all opponents' assists simultaneously. The answer becomes a_i + b_i.

Solution Approach

For the first two scenarios, use prefix sums for range queries. The challenge lies in efficiently querying maximum values and their positions.

For the third scenario, only the maximum values matter. Find max(a_i) and max(b_i), then compute their corresponding contributions on the opposite side.

Implementation

#include <bits/stdc++.h>
using namespace std;

const long long MOD = 1e9 + 7;
const int MAXN = 200005;

int playerCount, queryCount;
vector<long long> goals, assists;
vector<long long> prefixGoals, prefixAssists;
vector<pair<long long, int>> segMax1, segMax2;

struct SegmentTree {
    void construct(int node, int left, int right) {
        if (left == right) {
            segMax1[node] = {goals[left], left};
            segMax2[node] = {assists[left], left};
            return;
        }
        int mid = (left + right) >> 1;
        construct(node << 1, left, mid);
        construct(node << 1 | 1, mid + 1, right);
        segMax1[node] = max(segMax1[node << 1], segMax1[node << 1 | 1]);
        segMax2[node] = max(segMax2[node << 1], segMax2[node << 1 | 1]);
    }
    
    pair<long long, int> queryMaxGoals(int node, int ql, int qr, int left, int right) {
        if (ql <= left && right <= qr) return segMax1[node];
        int mid = (left + right) >> 1;
        pair<long long, int> result = {0, 0};
        if (ql <= mid) result = queryMaxGoals(node << 1, ql, qr, left, mid);
        if (qr > mid) result = max(result, queryMaxGoals(node << 1 | 1, ql, qr, mid + 1, right));
        return result;
    }
    
    pair<long long, int> queryMaxAssists(int node, int ql, int qr, int left, int right) {
        if (ql <= left && right <= qr) return segMax2[node];
        int mid = (left + right) >> 1;
        pair<long long, int> result = {0, 0};
        if (ql <= mid) result = queryMaxAssists(node << 1, ql, qr, left, mid);
        if (qr > mid) result = max(result, queryMaxAssists(node << 1 | 1, ql, qr, mid + 1, right));
        return result;
    }
};

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    
    freopen("max.in", "r", stdin);
    freopen("max.out", "w", stdout);
    
    cin >> playerCount >> queryCount;
    goals.resize(playerCount + 1);
    assists.resize(playerCount + 1);
    prefixGoals.resize(playerCount + 1);
    prefixAssists.resize(playerCount + 1);
    segMax1.resize(playerCount * 4 + 5);
    segMax2.resize(playerCount * 4 + 5);
    
    for (int i = 1; i <= playerCount; i++) {
        cin >> goals[i];
        prefixGoals[i] = prefixGoals[i - 1] + goals[i];
    }
    for (int i = 1; i <= playerCount; i++) {
        cin >> assists[i];
        prefixAssists[i] = prefixAssists[i - 1] + assists[i];
    }
    
    SegmentTree st;
    st.construct(1, 1, playerCount);
    
    for (int i = 0; i < queryCount; i++) {
        int l1, r1, l2, r2;
        cin >> l1 >> r1 >> l2 >> r2;
        
        auto maxGoal = st.queryMaxGoals(1, l1, r1, 1, playerCount);
        auto maxAssist = st.queryMaxAssists(1, l2, r2, 1, playerCount);
        
        long long sumGoals = prefixGoals[r1] - prefixGoals[l1 - 1];
        long long sumAssists = prefixAssists[r2] - prefixAssists[l2 - 1];
        
        long long scenario3a = maxGoal.first + assists[maxGoal.second - l1 + l2];
        long long scenario3b = goals[maxAssist.second - l2 + l1] + maxAssist.first;
        
        long long answer = max({sumGoals, sumAssists});
        if (maxGoal.second - l1 != maxAssist.second - l2) {
            answer = max(answer, max(scenario3a, scenario3b));
        } else {
            answer = max(answer, maxGoal.first + maxAssist.first);
        }
        cout << answer << '\n';
    }
    return 0;
}

Complexity Analysis

Preprocessing builds a segment tree in O(n). Each query performs two range maximum queries and computes prefix sums in O(log n + 1) time.


Problem 2: Combinatorial Sum Queries

Problem Overview

Calculate the sum of binomial coefficients where indices are contsrained by a step value k: sum over all i in [0, n] where i mod k = r of C(i, x).

Mathematical Foundation

Define f_{x,r} as the answer for fixed x and remainder r. Observe the recurrence relation by examining the Pascal's triangle structure:

f_{x,r} = f_{x-1,r-1} + f_{x,r-1}

This forms a triangular DP with O(nk) preprocessing.

Handling Edge Cases

The base case f_{x,0} requires special treatment. Consider the total sum for a fixed x across all remainders equals C(n+1, x+1).

The relationship f_{x,r} = sum_{i=0}^{r-1} f_{x,i} + f_{x,0} holds, allowing us to solve for f_{x,0} by subtracting contributions.

Algorithm Selection

Apply sqrt decomposition based on k:

  • When k² > n: Direct enumeration O(n/k) per query
  • When k² ≤ n: Precompute using DP in O(nk) time

Implementation

#include <bits/stdc++.h>
using namespace std;

const long long MOD = 1e9 + 7;
const int MAXN = 100005;

long long powerMod(long long a, long long e) {
    long long result = 1;
    while (e) {
        if (e & 1) result = result * a % MOD;
        a = a * a % MOD;
        e >>= 1;
    }
    return result;
}

int totalN, totalQ;
long long stepK;
vector<long long> factorial, invFactorial;
vector<array<long long, 400>> dpTable;
vector<long long> tempRow;

void initialize() {
    factorial[0] = 1;
    for (int i = 1; i <= totalN + 1; i++) {
        factorial[i] = factorial[i - 1] * i % MOD;
    }
    invFactorial[totalN + 1] = powerMod(factorial[totalN + 1], MOD - 2);
    for (int i = totalN; i >= 0; i--) {
        invFactorial[i] = invFactorial[i + 1] * (i + 1) % MOD;
    }
}

inline long long binomial(int n, int r) {
    if (n < r) return 0;
    return factorial[n] * invFactorial[r] % MOD * invFactorial[n - r] % MOD;
}

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    
    freopen("sum.in", "r", stdin);
    freopen("sum.out", "w", stdout);
    
    cin >> totalN >> stepK >> totalQ;
    
    factorial.resize(totalN + 2);
    invFactorial.resize(totalN + 2);
    dpTable.resize(totalN + 1);
    tempRow.resize(stepK);
    
    initialize();
    
    if (stepK * stepK > totalN) {
        while (totalQ--) {
            int x, r;
            cin >> x >> r;
            long long result = 0;
            for (int i = r; i <= totalN; i += stepK) {
                result += binomial(i, x);
                if (result >= MOD) result -= MOD;
            }
            cout << result << '\n';
        }
    } else {
        for (int i = 0; i < stepK; i++) {
            dpTable[0][i] = totalN / stepK;
        }
        
        long long invStep = powerMod(stepK, MOD - 2);
        
        for (int row = 1; row <= totalN; row++) {
            long long cumulative = 0;
            for (int col = 1; col < stepK; col++) {
                tempRow[col] = dpTable[row - 1][col - 1];
                tempRow[col] += tempRow[col - 1];
                if (tempRow[col] >= MOD) tempRow[col] -= MOD;
                cumulative += tempRow[col];
                if (cumulative >= MOD) cumulative -= MOD;
            }
            dpTable[row][0] = (binomial(totalN, row + 1) - cumulative + MOD) % MOD * invStep % MOD;
            
            for (int col = 1; col < stepK; col++) {
                dpTable[row][col] = dpTable[row][0] + tempRow[col];
                if (dpTable[row][col] >= MOD) dpTable[row][col] -= MOD;
            }
        }
        
        while (totalQ--) {
            int x, r;
            cin >> x >> r;
            long long answer = dpTable[x][r];
            if (r == 0) {
                answer += binomial(totalN, x);
                answer %= MOD;
            }
            cout << answer << '\n';
        }
    }
    return 0;
}

Complexity Analysis

For large step values: O(q × n/k) per query.

For small step values: O(nk) preprocessing plus O(1) per query.

The algorithm achieves optimal performance by adapting to the value of k through sqrt decomposition.

Tags: Competitive Programming segment tree combinatorics Dynamic Programming range queries

Posted on Sun, 24 May 2026 16:31:07 +0000 by KingIsulgard