Processing math: 100%
(Analysis by Chongtian Ma, Alex Liang, and Patrick Deng)

Subtask 1: N2000.

We can directly simulate the process according to the problem statement. We can keep track of the amount of milk that each cow has and update in O(N) each minute leading to an O(N2) solution.

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

int main(){
    ios_base::sync_with_stdio(0); cin.tie(0); 
    int n;
    cin >> n;

    vector<int> cap(n), cur(n); 
    
    for (int i = 0; i < n; i++){
        cin >> cap[i];
        cur[i] = cap[i];
    }
    
    for (int minute = 1; minute <= n; minute++){
        rotate(cur.begin(), cur.begin() + n - 1, cur.end());

        for (int i = 0; i < n; i++)
            cur[i] = min(cur[i], cap[i]);

        cout<<accumulate(cur.begin(), cur.end(), 0LL)<<"\n";
    }
}

Subtask 2: ai2.

Each bucket has capacity of either 1 or 2 in this subtask. Consider a cow that starts off with 2 unit of milk. That specific 2 units of milk will keep getting passed until it reaches a bucket with capacity 1 where it will be reduced to 1 unit of milk.

For each ai=2 we want to find the time t in which it will reach a bucket with capacity 1. Then we know that 1 milk is lost at time t. We can find t by finding the first aj=1 on the right of i.

We can rotate the array such that the minimum element appears at the end. Iterating over the array backwards and keeping track of the most recent 1 leads to an O(N) solution.

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

int main(){
    ios_base::sync_with_stdio(0); cin.tie(0); 
    int n;
    cin >> n;

    vector<int> A(n); 
    vector<int> reduce(n + 5, 0);

    int sum = 0;

    for (int &i : A){
        cin >> i;
        sum += i;
    }
    rotate(A.begin(), next(min_element(A.begin(), A.end())), A.end());
    
    for (int i = n - 1, lst = -1; i >= 0; i--){
        if (A[i] == 1)
            lst = i;
        if (A[i] == 2 and lst != -1){
            // At time lst - i the 2 will get reduced to a 1
            reduce[lst - i]++;
        }
    }
    
    for (int i = 1; i <= n; i++){
        sum -= reduce[i];
        cout<<sum<<"\n";
    }
}

Subtask 3: All ai are generated uniformly at random in the range [1,109].

Let's rotate the array such that the minimum element appears at the end.

In the previous subtask, for each ai=2, we wanted to find when that specific 2 units of milk will reach a bucket with capacity 1. We can extend this reasoning. For some ai, we want to find all times in which that milk will get reduced.

To find these values, we can iterate over the array backwards and keep a monotonic stack. At each element, we can iterate over the entire stack and update our answer.

Because all ai are generated uniformly at random, the expected size of the monotonic stack at each i is O(logN). This is since the probability of j (ij<i+N) being in the stack is at most 1ji+1 since aj must be smaller than [ai,ai+1,...,aj1]. By the linearity of expectation, the expected size of the stack is 11+12++1N=O(logN) by the harmonic series. Thus, the solution runs in O(NlogN) time.

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

#define ll long long

int main(){
    ios_base::sync_with_stdio(0); cin.tie(0); 
    int n;
    cin >> n;

    vector<int> A(n); 
    ll sum = 0;

    for (int &i : A){
        cin >> i;
        sum += i;
    }
    rotate(A.begin(), next(min_element(A.begin(), A.end())), A.end());

    vector<int> stk;
    vector<ll> reduce(n + 5, 0);

    auto procStack = [&](int pos){
        for (int i = 1; i < stk.size(); i++){
            int delta = A[stk[i]] - A[stk[i - 1]];
            reduce[stk[i - 1] - pos] += delta; 
        }
    };

    for (int i = n - 1; i >= 0; i--){
        while (stk.size() and A[stk.back()] >= A[i]){
            stk.pop_back();
        }
        stk.push_back(i);
        procStack(i);
    }

    for (int i = 1; i <= n; i++){
        sum -= reduce[i];
        cout<<sum<<"\n";
    }
}

Full Solution 1:

Let's look at what is redundant in our subtask 3 solution. Suppose the bottom 2 elements of the stack remain there for a long time. Then we will be iterating over them each time we process the stack.

Let's speed up our subtask 3 solution. Consider some adjacent pair of elements (jp1,jp) in the stack. We know that d=ajp1ajp milk is lost by going from jp1 to jp. Let's look at all i (1iN) where (jp1,jp) is in the stack at i (meaning the milk originating from i will be reduced by d at time jpi). Clearly, all such i will form an interval.

When we remove an element from the top of the stack, we can get the interval it was active for and perform a range add to keep track of how much milk is lost at what time. This leads to an O(N) solution.

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

#define ll long long

int main(){
    ios_base::sync_with_stdio(0); cin.tie(0); 
    int n;
    cin >> n;

    vector<int> A(n); 
    ll sum = 0;

    for (int &i : A){
        cin >> i;
        sum += i;
    }
    rotate(A.begin(), next(min_element(A.begin(), A.end())), A.end());

    vector<int> stk;
    vector<ll> reduce(n + 5, 0);

    for (int i = n - 1; i >= 0; i--){
        while (stk.size() and A[stk.back()] >= A[i]){
            if (stk.size() > 1){
                // i < t1 < t2
                int t1 = stk.back();
                int t2 = stk[stk.size() - 2];

                // Milk lost from going from t1 to t2
                int delta = A[t1] - A[t2];
                
                // When does the pair begin applying delta
                int st = t2 - t1;

                // For how long does the pair apply delta
                int sz = t1 - i;

                reduce[st] += delta;
                reduce[st + sz] -= delta;
            }
            stk.pop_back();
        }
        stk.push_back(i);
    }

    // Finish proccessing rest of stack
    while (stk.size() > 1){
        int t1 = stk.back();
        int t2 = stk[stk.size() - 2];

        int delta = A[t1] - A[t2];
        int st = t2 - t1;
        int sz = t1 + 1;

        reduce[st] += delta;
        reduce[st + sz] -= delta;

        stk.pop_back();
    }

    // Get answer
    for (int i = 1; i <= n; i++){
        reduce[i] += reduce[i - 1];
        sum -= reduce[i];

        cout<<sum<<"\n";
    }
}

Full Solution 2:

For each i, we want to find the number of buckets with milk ai at each minute. To deal with the case where there are multiple same ai, we only consider the number of buckets equal to ai whose milk was specifically reduced (by a non-zero amount) to ai by the bucket at i (we also count the milk starting at i).

Let li be the index of the first element less than or equal to ai on the left of i. Consider all elements strictly between li and i. All these elements will be greater than ai and thus will be reduced to ai when they reach i. Suppose there are s such elements including ai. Then we will gain one additional element equal to ai at times 0,1,...,s1 (if we suppose we start off with 0 elements equal to ai).

Let wi store the total amount of milk at time i. We should perform the following update:

We also know that buckets with ai milk can be reduced to a smaller value. Let ri be the index of the first element strictly less than ai on the right of i. Let time t be the time for the milk originating at i to reach ri. Then we will lose one element equal to ai at times t,t+1,...,t+s1. A similar update to the one above should be performed.

We can use a monotonic stack to find li and ri. Implementation can be made easier by prepending and appending the array to itself. We can use prefix sums of prefix sums to perform the updates on w efficiently. See the following example:

Thus, this solution runs in O(N) time.

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

int main(){
    ios_base::sync_with_stdio(0); cin.tie(0);
    int n;
    cin >> n;

    vector<int> A(n);

    for (int &i : A)
        cin >> i;
    
    for (int i = 0; i < 2 * n; i++)
        A.push_back(A[i]);
    
    // L is first <= on left and R is first < on right
    vector<int> L(A.size()), R(A.size());
    {
        stack<int> stk;
        for (int i = 0; i < A.size(); i++){
            while (stk.size() and A[i] < A[stk.top()])
                stk.pop();

            L[i] = stk.empty() ? -1 : stk.top();
            stk.push(i);
        }
    }
    {
        stack<int> stk;
        for (int i = (int)A.size() - 1; i >= 0; i--){
            while (stk.size() and A[i] <= A[stk.top()])
                stk.pop();
            
            R[i] = stk.empty() ? -1 : stk.top();
            stk.push(i);
        }
    }

    vector<ll> psm(n + 5, 0);

    for (int i = n; i < 2 * n; i++){
        // Gain A[i] during minutes [0, sz - 1]
        int sz = i - L[i];
        psm[0] += A[i];
        psm[sz] -= A[i];

        // Lose A[i] during minutes [R[i] - i, R[i] - i + sz - 1]
        if (R[i] != -1){
            psm[R[i] - i] -= A[i];
            psm[R[i] - i + sz] += A[i];
        }
    }

    for (int i = 1; i <= n; i++)
        psm[i] += psm[i - 1];

    for (int i = 1; i <= n; i++){
        psm[i] += psm[i - 1];
        cout<<psm[i]<<"\n";
    }
}