Solutions to Competitive Programming Problems

Problem A: Graph Coloring

We are given an integer n and need to color the integers from 1 to n. The constraint is that for any two integers i and j where i < j, if their difference j - i is a prime number, they must have differant colors. The goal is to use the minimum number of colors possible and provide a valid coloring scheme.

For n >= 7, it can be proven that the minimum number of colors required is 4. A valid coloring scheme is to repeat the sequence 1, 2, 3, 4. In this pattern, any two numbers with the same color will have a difference that is a multiple of 4. Since 4 is the first composite number, any multiple of 4 greater than 4 itself cannot be a prime number, thus satisfying the constraint. For smaller values of n (from 1 to 6), we can determine the optimal number of colors by manual inspection.

#include <bits>
using namespace std;

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

    int n;
    cin >> n;

    // Precomputed optimal color counts for small n
    int color_counts[] = {1, 1, 2, 2, 3, 3};
    string colorings[] = {
        "1",
        "1 1",
        "1 1 2",
        "1 1 2 2",
        "1 1 2 2 3",
        "1 1 2 2 3 3"
    };

    if (n <= 6) {
        cout << color_counts[n - 1] << "
" << colorings[n - 1] << "
";
    } else {
        cout << "4
";
        for (int i = 1; i <= n; ++i) {
            cout << (i - 1) % 4 + 1 << " ";
        }
        cout << "
";
    }

    return 0;
}
</bits>

Problem B: Sequence Construction

Given a sequence of integers a of length m, an integer n, and a budget D, we need to construct a sequence b of the same length. Each element b_i must be in the range [0, n], and the sum of products sum(a_i * b_i) must not exceed D. The objective is to maximize the value of sum(b_i) + k * min(b_i), where k is a given constant.

The objective function, when viewed as a function of the minimum value mn in the sequence b, is a unimodal function (it first increases, reaches a peak, and then decreases). This property allows us to use ternary search to efficiently find the optimal value for mn that maximizes the objective.

#include <bits>
using namespace std;

using ll = long long;

ll n, k, D;
int m;
vector<ll> a;
vector<ll> prefix_sum;

ll calculate_objective(ll mn) {
    if (mn < 0) return 0;
    ll total = mn * k + mn * m;
    ll remaining_budget = D - mn * prefix_sum.back();

    if (remaining_budget < 0) return 0;

    // Find the largest index where (n - mn) * a[i] <= remaining_budget
    int l = 0, r = m - 1, pos = 0;
    while (l <= r) {
        int mid = l + (r - l) / 2;
        if ((n - mn) * a[mid] <= remaining_budget) {
            pos = mid;
            l = mid + 1;
        } else {
            r = mid - 1;
        }
    }

    total += (n - mn) * pos;
    remaining_budget -= (n - mn) * a[pos];

    // Use remaining budget for the next element
    if (pos + 1 < m) {
        ll additional = min(n - mn, remaining_budget / a[pos + 1]);
        total += additional;
    }

    return total;
}

ll ternary_search(ll low, ll high) {
    while (high - low > 2) {
        ll mid1 = low + (high - low) / 3;
        ll mid2 = high - (high - low) / 3;

        ll val1 = calculate_objective(mid1);
        ll val2 = calculate_objective(mid2);

        if (val1 >= val2) {
            high = mid2;
        } else {
            low = mid1;
        }
    }
    // Check a few points around the final range for safety
    ll best = 0;
    for (ll x = low; x <= high; ++x) {
        best = max(best, calculate_objective(x));
    }
    return best;
}

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

    int T;
    cin >> T;
    while (T--) {
        cin >> n >> m >> k >> D;
        a.resize(m);
        prefix_sum.resize(m + 1, 0);

        for (int i = 0; i < m; ++i) {
            cin >> a[i];
        }
        sort(a.begin(), a.end());
        for (int i = 0; i < m; ++i) {
            prefix_sum[i + 1] = prefix_sum[i] + a[i];
        }

        ll optimal_mn = ternary_search(0, min(n, D / prefix_sum.back()));
        cout << calculate_objective(optimal_mn) << "
";
    }

    return 0;
}
</ll></ll></bits>

Problem C: Path Queries on a Tree

Given a tree with n nodes, we need to answer m queries. Each query provides two nodes l and r, and we must count the number of nodes k on the path from l to r such that the number of steps taken to reach k from l is exactly equal to the value of k.

The path from l to r can be split into two segments: from l to the Lowest Common Ancestor (LCA) and from the LCA to r. For the first segment, the condition dist(l, k) = k translates to dep[l] = dep[k] + k. For the second segment, the condition becomes dep[k] - k = 2 * dep[lca] - dep[l]. By defining appropriate offsets, we can use tree decomposition (heavy-light decomposition) combined with a Persistent Segment Tree to preprocess the tree and answer each query in logarithmic time.

#include <bits>
using namespace std;

const int MAXN = 3e5 + 10;
const int OFFSET = 3e5;

int n, m;
vector<int> adj[MAXN];
int depth[MAXN], parent[MAXN], heavy[MAXN], head[MAXN], pos[MAXN], timer;

struct PersistentSegmentTree {
    struct Node {
        int left, right, count;
    };
    vector<node> nodes;
    vector<int> roots;

    PersistentSegmentTree(int size) : nodes(1), roots(size + 1) {}

    int new_node() { nodes.push_back({0, 0, 0}); return nodes.size() - 1; }

    void build(int v, int tl, int tr) {
        if (tl == tr) return;
        int tm = (tl + tr) / 2;
        nodes[v].left = new_node();
        nodes[v].right = new_node();
        build(nodes[v].left, tl, tm);
        build(nodes[v].right, tm + 1, tr);
    }

    void init(int size) {
        roots[0] = new_node();
        build(0, 1, size);
    }

    int update(int v, int tl, int tr, int idx) {
        int u = new_node();
        nodes[u] = nodes[v];
        if (tl == tr) {
            nodes[u].count++;
            return u;
        }
        int tm = (tl + tr) / 2;
        if (idx <= tm) {
            nodes[u].left = update(nodes[v].left, tl, tm, idx);
        } else {
            nodes[u].right = update(nodes[v].right, tm + 1, tr, idx);
        }
        nodes[u].count = nodes[nodes[u].left].count + nodes[nodes[u].right].count;
        return u;
    }

    int query(int v, int u, int tl, int tr, int l, int r) {
        if (r < tl || tr < l) return 0;
        if (l <= tl && tr <= r) return nodes[u].count - nodes[v].count;
        int tm = (tl + tr) / 2;
        return query(nodes[v].left, nodes[u].left, tl, tm, l, r) +
               query(nodes[v].right, nodes[u].right, tm + 1, tr, l, r);
    }
};

void dfs(int v, int p = -1) {
    parent[v] = p;
    heavy[v] = -1;
    int max_size = 0;
    for (int u : adj[v]) {
        if (u == p) continue;
        depth[u] = depth[v] + 1;
        dfs(u, v);
        if (max_size < adj[u].size()) {
            max_size = adj[u].size();
            heavy[v] = u;
        }
    }
}

void decompose(int v, int h) {
    head[v] = h;
    pos[v] = ++timer;
    if (heavy[v] != -1) {
        decompose(heavy[v], h);
    }
    for (int u : adj[v]) {
        if (u != parent[v] && u != heavy[v]) {
            decompose(u, u);
        }
    }
}

int lca(int u, int v) {
    while (head[u] != head[v]) {
        if (depth[head[u]] > depth[head[v]]) swap(u, v);
        v = parent[head[v]];
    }
    return depth[u] < depth[v] ? u : v;
}

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

    cin >> n >> m;
    for (int i = 1; i < n; ++i) {
        int u, v;
        cin >> u >> v;
        adj[u].push_back(v);
        adj[v].push_back(u);
    }

    depth[1] = 1;
    dfs(1);
    decompose(1, 1);

    PersistentSegmentTree pst(2 * n + OFFSET);
    pst.init(2 * n + OFFSET);

    for (int i = 1; i <= n; ++i) {
        int val1 = pos[i] + depth[i] + OFFSET;
        pst.roots[i] = pst.update(pst.roots[i - 1], 1, 2 * n + OFFSET, val1);
    }

    for (int i = 1; i <= m; ++i) {
        int l, r;
        cin >> l >> r;
        int ancestor = lca(l, r);
        int ans = 0;

        // Query path l to lca
        while (head[l] != head[ancestor]) {
            ans += pst.query(pst.roots[pos[head[l]] - 1], pst.roots[pos[l]], 1, 2 * n + OFFSET, depth[l] + OFFSET, 2 * n + OFFSET);
            l = parent[head[l]];
        }
        ans += pst.query(pst.roots[pos[ancestor] - 1], pst.roots[pos[l]], 1, 2 * n + OFFSET, depth[l] + OFFSET, 2 * n + OFFSET);

        // Query path r to lca
        pst = PersistentSegmentTree(2 * n + OFFSET); // Re-initialize for second query
        pst.init(2 * n + OFFSET);
        for (int j = 1; j <= n; ++j) {
            int val2 = depth[j] - pos[j] + OFFSET;
            pst.roots[j] = pst.update(pst.roots[j - 1], 1, 2 * n + OFFSET, val2);
        }

        while (head[r] != head[ancestor]) {
            ans += pst.query(pst.roots[pos[head[r]] - 1], pst.roots[pos[r]], 1, 2 * n + OFFSET, 1, 2 * depth[ancestor] - depth[l] + OFFSET);
            r = parent[head[r]];
        }
        ans += pst.query(pst.roots[pos[ancestor] - 1], pst.roots[pos[r]], 1, 2 * n + OFFSET, 1, 2 * depth[ancestor] - depth[l] + OFFSET);

        // Adjust for the LCA node if it was counted twice
        if (ancestor + depth[ancestor] == depth[l]) ans--;
        
        cout << ans << "
";
    }

    return 0;
}
</int></node></int></bits>

Problem D: Counting Unique Subarrays

We have a sequence of integers. The sequence supports two operations: a single-point update (changing the value of one element) and a query. A query asks for the number of subarrays within a given range [l, r] that contain no duplicate elements.

To solve this, we can preprocess an array max_right[i] where max_right[i] represents the rightmost index of the longest subarray starting at i that contains all unique elements. This array can be maintained efficiently using a two-pointer sliding window technique. For each update, we recompute the affected segments of max_right. For a query [l, r], the answer is the sum of min(max_right[i], r) - i + 1 for all i from l to r.

#include <bits>
using namespace std;

const int MAXN = 2e5 + 10;

int n, m;
int a[MAXN];
int max_right[MAXN];

void compute_max_right() {
    vector<bool> seen(n + 2, false);
    int left_ptr = 1, right_ptr = 0;
    while (left_ptr <= n) {
        while (right_ptr < n && !seen[a[right_ptr + 1]]) {
            seen[a[++right_ptr]] = true;
        }
        max_right[left_ptr] = right_ptr;
        seen[a[left_ptr++]] = false;
    }
}

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

    cin >> n >> m;
    for (int i = 1; i <= n; ++i) {
        cin >> a[i];
    }
    compute_max_right();

    while (m--) {
        int op, l, r;
        cin >> op >> l >> r;
        if (op == 1) {
            a[l] = r;
            compute_max_right();
        } else if (op == 2) {
            long long ans = 0;
            for (int i = l; i <= r; ++i) {
                ans += min(max_right[i], r) - i + 1;
            }
            cout << ans << "
";
        }
    }

    return 0;
}
</bool></bits>

Tags: competitive-programming algorithm data-structure ternary-search segment-tree

Posted on Wed, 19 Aug 2026 16:36:35 +0000 by cool30