Algorithmic Solutions for Codeforces Round 1068

A. Sleeping Through Classes

This problem can be solved using a brute-force approach. Given the constraints, iterating through all possible scenarios or simulating the process directly will yield the correct solution within the time limit.

B. Niko's Tactical Cards

The optimal strategy for this problem involves dynamic programming. By sorting the cards based on their maximum and minimum values, we can process them sequentially and maintain a DP state that tracks the best possible outcome up to the current card.

C. Kanade's Perfect Multiples

Problem Statement: Given a sequence $a$ of length $n$ ($1 \le a_i \le k$) and an integer $k$, find the smallest set $B$ such that every $a_i$ has at least one divisor in $B$. Additionally, for every $b_j \in B$, all multiples of $b_j$ less than or equal to $k$ must exist in the sequence $a$. If no such set exists, return -1.

Solution: We can use a greedy approach. First, sort the sequence $a$ in ascending order. We then iterate through the sorted elements. If an element has not been marked as "covered" (meaning it is not a multiple of any previously selected number for $B$), we add it to our result set $B$. Once added, we must verify that all multiples of this number (up to $k$) exist in the original sequence. We iterate through the multiples and check thier presence in a frequency map. If a required multiple is missing, the construction is impossible, and we return -1. Otherwise, we mark these multiples as covered to skip them in future iterations.

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

typedef long long ll;

void solve() {
    ll n, k;
    cin >> n >> k;
    vector<ll> arr(n);
    for (int i = 0; i < n; ++i) cin >> arr[i];
    
    sort(arr.begin(), arr.end());
    
    map<ll, int> availability;
    for (auto val : arr) availability[val] = 1;
    
    vector<ll> selectedSet;
    for (int i = 0; i < n; ++i) {
        ll current = arr[i];
        if (availability[current] == 1) {
            selectedSet.push_back(current);
            // Check all multiples
            for (ll mult = current; mult <= k; mult += current) {
                if (availability.find(mult) == availability.end()) {
                    cout << -1 << endl;
                    return;
                }
                availability[mult] = 0; // Mark as covered
            }
        }
    }

    cout << selectedSet.size() << "\n";
    for (auto val : selectedSet) cout << val << " ";
    cout << "\n";
}

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    int t;
    cin >> t;
    while (t--) {
        solve();
    }
    return 0;
}

D. Taiga's Carry Chains

Problem Statement: Given an integer $n$ and $k$ operations, in each operation you can add $2^l$ (where $l \ge 0$) to the number. If the addition generates binary carries, you gain points equal to the number of carries. Maximize the score.

Solution: For $n=0$, the optimal strategy is to create a sequence of $k-1$ ones, resulting in $k-1$ points. For $n \neq 0$, we analyze the binary representation. If the number of zeros ($m$) between the most significant bit and the least significant bit is such that $k \ge m + 1$, we can fill all gaps to create a contiguous block of ones and then trigger a chain reaction, yielding $k - 1 + (\text{number of ones in } n)$. If $k$ is smaller, we use Dynamic Programming. Let $dp[i][j][b]$ be the maximum score using $j$ operations up to the $i$-th bit, with the current bit state being $b$. The transitions handle filling zeros or propagating carries to higher bits.

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

typedef long long ll;

ll dp[35][35][2];

void solve() {
    ll n, k;
    cin >> n >> k;
    
    vector<int> bits(35, 0);
    int maxBit = 0;
    ll temp = n;
    while (temp) {
        if (temp & 1) bits[maxBit] = 1;
        temp >>= 1;
        maxBit++;
    }
    maxBit--;

    int zeroCount = 0;
    for (int i = 0; i <= maxBit; ++i) {
        if (bits[i] == 0) zeroCount++;
    }

    if (k >= zeroCount + 1) {
        cout << k + (maxBit + 1) - zeroCount - 1 << endl;
        return;
    }

    memset(dp, -1, sizeof(dp));
    dp[0][0][0] = 0;
    dp[0][0][1] = 0;

    for (int i = 0; i <= maxBit; ++i) {
        for (int j = 0; j <= k; ++j) {
            // Transition from previous bit
            if (i > 0) {
                dp[i][j][bits[i]] = max(dp[i][j][bits[i]], dp[i-1][j][0]);
                dp[i][j][bits[i]] = max(dp[i][j][bits[i]], dp[i-1][j][1]);
            } else {
                dp[i][j][bits[i]] = max(dp[i][j][bits[i]], 0LL);
            }

            // Try to create carries
            if (j == k) continue;
            
            int carryLen = 1;
            // Logic to handle the chain of carries
            // If we start a carry at i, it propagates until it hits a 1
            // This logic is simplified for brevity, the full DP handles indices carefully
            int nextIdx = i + 1;
            while (nextIdx <= maxBit + 1) {
                if (bits[nextIdx] == 0) {
                    if (j + carryLen <= k) {
                        ll gain = nextIdx - i;
                        dp[nextIdx][j + carryLen][1] = max(dp[nextIdx][j + carryLen][1], dp[i][j][bits[i]] + gain);
                    }
                    carryLen++;
                } else {
                    if (j + carryLen <= k) {
                         ll gain = nextIdx - i;
                        dp[nextIdx][j + carryLen][1] = max(dp[nextIdx][j + carryLen][1], dp[i][j][bits[i]] + gain);
                    }
                    break; 
                }
                nextIdx++;
            }
        }
    }

    ll result = 0;
    for (int i = 0; i <= maxBit + 1; ++i) {
        for (int j = 0; j <= k; ++j) {
            result = max(result, dp[i][j][0]);
            result = max(result, dp[i][j][1]);
        }
    }
    cout << result << endl;
}

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    int t;
    cin >> t;
    while (t--) {
        solve();
    }
    return 0;
}

E. Shiro's Mirror Duel

Problem Statement: Sort a permutation $p$ of length $n$ using a specific operation. The operation takes two indices $x$ and $y$. With 50% probability, it swaps $p_x$ and $p_y$; with the remaining 50% probability, it swaps $p_{n-x+1}$ and $p_{n-y+1}$. The limit on operations is $2.5n + 800$.

Solution: The key observation is that the two possible swap targets are symmetric with respect to the center of the array. To avoid disturbing already sorted elements, we adopt a symmetric sorting strategy. We use two pointers, $left$ starting at 1 and $right$ starting at $n$. We attempt to place the correct values at $p[left]$ and $p[right]$ simultaneously. Because the operations are symmetric, any swap involving the symmetric counterparts will only affect the current pair of positions being fixed, leaving the rest of the sorted array intact. The expected number of operations to fix a pair $(i, n-i+1)$ is 5, leading to a total expectation of $2.5n$.

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

typedef long long ll;

void solve() {
    int n;
    cin >> n;
    vector<int> perm(n + 1);
    vector<int> pos(n + 1); // Stores index of each value
    
    for (int i = 1; i <= n; ++i) {
        cin >> perm[i];
        pos[perm[i]] = i;
    }

    auto query = [&](int x, int y) {
        cout << "? " << x << " " << y << endl;
        cout.flush();
        int a, b;
        cin >> a >> b;
        return make_pair(a, b);
    };

    int left = 1, right = n;
    while (left <= right) {
        bool sorted = true;
        if (perm[left] != left) {
            sorted = false;
            int targetVal = left;
            int idx1 = left;
            int idx2 = pos[targetVal];
            
            auto [u, v] = query(idx1, idx2);
            
            // Update position map and permutation
            pos[perm[u]] = v;
            pos[perm[v]] = u;
            swap(perm[u], perm[v]);
        }
        
        if (perm[right] != right) {
            sorted = false;
            int targetVal = right;
            int idx1 = right;
            int idx2 = pos[targetVal];
            
            auto [u, v] = query(idx1, idx2);
            
            pos[perm[u]] = v;
            pos[perm[v]] = u;
            swap(perm[u], perm[v]);
        }

        if (sorted) {
            left++;
            right--;
        }
    }
    cout << "!" << endl;
    cout.flush();
}

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    int t;
    cin >> t;
    while (t--) {
        solve();
    }
    return 0;
}

F. Range Queries on Monotonic Sequences

Problem Statement: Given a non-increasing array $a$, process $q$ queries. Each query $(l, r, x)$ asks to simulate a process: initialize $sum = 0$, iterate from $l$ to $r$, adding $a_i$ to $sum$. If $sum \ge x$, increment a counter $X$ and reset $sum$ to 0. Output $X$ and the final remainder $sum = Y$.

Solution: A crucial observation is that the length of a segment required to reach the threshold $x$ (denoted as $k$) is non-increasing as we move through the array. This property allows us to use a square root decomposition strategy. We set a block size $B = \sqrt{n}$. 1. If the current segment length $k \ge B$, the number of such segments is small (at most $\sqrt{n}$). We can binary search for the end of each segment using a prefix sum array. 2. If $k < B$, the number of distinct values for $k$ is small. Since $k$ is non-increasing, equal values are contiguous. We can binary search to find how many consecutive segments have the same length $k$. By combining these two strategies based on the value of $k$, we achieve an acceptable time complexity.

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

typedef long long ll;

const int MAXN = 200005;
ll arr[MAXN];
ll prefixSum[MAXN];
int n, q;
int blockSize;

bool hasEnoughSum(int l, int r, ll threshold) {
    return (prefixSum[r] - prefixSum[l - 1]) >= threshold;
}

// Finds the minimum length starting from 'start' such that sum >= x
ll findMinSegmentLen(int start, int r, ll x, ll minLen) {
    ll low = minLen;
    ll high = r - start + 1;
    ll ans = high;
    
    while (low <= high) {
        ll mid = low + (high - low) / 2;
        int endIdx = start + mid - 1;
        if (hasEnoughSum(start, endIdx, x)) {
            ans = mid;
            high = mid - 1;
        } else {
            low = mid + 1;
        }
    }
    return ans;
}

// Finds how many consecutive segments of length 'len' fit the sum condition
ll countSegmentsOfLen(int start, int r, ll len, ll x) {
    ll low = 1;
    ll high = (r - start + 1) / len;
    ll ans = 0;
    
    while (low <= high) {
        ll mid = low + (high - low) / 2;
        int lBound = start + len * (mid - 1);
        int rBound = start + len * mid - 1;
        
        if (rBound > r) {
            high = mid - 1;
            continue;
        }
        
        if (hasEnoughSum(lBound, rBound, x)) {
            ans = mid;
            low = mid + 1;
        } else {
            high = mid - 1;
        }
    }
    return ans;
}

void solve() {
    cin >> n >> q;
    blockSize = sqrt(n);
    for (int i = 1; i <= n; ++i) {
        cin >> arr[i];
        prefixSum[i] = prefixSum[i - 1] + arr[i];
    }

    while (q--) {
        int l, r;
        ll x;
        cin >> l >> r >> x;
        
        ll count = 0;
        ll remainder = 0;
        bool largeSegmentMode = false;
        
        int currentIdx = l;
        ll currentLen = 1;
        
        while (currentIdx <= r) {
            if (!largeSegmentMode) {
                // Determine the smallest segment length starting at currentIdx
                currentLen = findMinSegmentLen(currentIdx, r, x, currentLen);
                
                int endIdx = currentIdx + currentLen - 1;
                if (endIdx >= r) {
                    remainder = prefixSum[r] - prefixSum[currentIdx - 1];
                    if (remainder >= x) {
                        count++;
                        remainder = 0;
                    }
                    break;
                }
                
                // Check if we can skip many segments of this length
                ll segments = countSegmentsOfLen(currentIdx, r, currentLen, x);
                count += segments;
                currentIdx += segments * currentLen;
                
                if (currentLen >= blockSize) {
                    largeSegmentMode = true;
                }
            } else {
                // Large segment mode: handle one by one (few iterations)
                currentLen = findMinSegmentLen(currentIdx, r, x, currentLen);
                
                int endIdx = currentIdx + currentLen - 1;
                if (endIdx >= r) {
                    remainder = prefixSum[r] - prefixSum[currentIdx - 1];
                    if (remainder >= x) {
                        count++;
                        remainder = 0;
                    }
                    break;
                }
                
                count++;
                currentIdx = endIdx + 1;
            }
        }
        cout << count << " " << remainder << "\n";
    }
}

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    solve();
    return 0;
}

Tags: Codeforces cpp algorithms dynamic-programming interactive-problem

Posted on Sun, 27 Sep 2026 16:29:03 +0000 by ready2drum