(Analysis by Akshaj Arora, edited by Benjamin Qi)

Let $T$ be the upper bound on $t_i$.

Subtask 1: $O(NT)$ per query

We can simulate the buckets for each query.

Subtask 2: $O(N)$ per query

We can observe that the buckets are periodic. Specifically, there exists a $b_i$ such that bucket $i$ is flipped at times $(i-1)+kb_i$ for all integers $k\geq 1$. We can calculate that $b_1=a_1+1$ and $b_i=\left\lceil\frac{a_i}{a_{i-1}}\right\rceil b_{i-1}$ for $i\geq 2$ since each bucket flips after some integer number of flips of the bucket above. For each query, we can calculate the periods for each bucket and then output the answer $a_N\cdot \left\lfloor\frac{\max(t-(N-1),0)}{b_N}\right\rfloor$.

Implementation note: One way to avoid integer overflow when computing $b_N$ (which can exceed $10^{18}$) is to instead maintain $\left\lfloor\frac{\max(t-(N-1),0)}{b_i}\right\rfloor$ as $i$ increases from $1$ to $N$ using division.

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

int cdiv(int a, int b) { return (a + b - 1) / b; }

const ll mx = 1e18;

void solve() {
    int n;
    cin >> n;
    vector<int> a(n);
    for (auto &i : a) cin >> i;

    // solve
    int q;
    cin >> q;
    while (q--) {
        ll i, v, t;
        cin >> i >> v >> t;
        a[--i] = v;

        ll ans = max(0LL, t - n + 1);
        ans /= a[0] + 1;
        for (int i = 1; i < n; ++i) {
            ll r = cdiv(a[i], a[i - 1]);
            ans /= r;
        }
        ans *= a.back();
        cout << ans << '\n';
    }
}

signed main() {
    cin.tie(0)->sync_with_stdio(0);
    solve();
}

Full solution: $O(\log_2 T)$ per query

When $a_i\leq a_{i-1}$, then $\left\lceil\frac{a_i}{a_{i-1}}\right\rceil=1$, so we can effectively ignore these terms. Hence, we only need to consider terms where $a_i>a_{i-1}$. For these terms, $\left\lceil\frac{a_i}{a_{i-1}}\right\rceil\geq 2$. If $b_N>10^{18}$, then the answer will always be $0$. Thus, the answer can only be nonzero if there exist at most $\log_2(10^{18})\le 60$ indexes $i$ such that $a_i>a_{i-1}$.

Store these indexes in a set. Each update adds or removes at most $2$ elements from the set. To answer a query, we manually calculate the answer using the set's indices or break if we detect that the answer is zero, which always happens within $60$ indices.

Akshaj's C++ code:

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

int cdiv(int a, int b) { return (a + b - 1) / b; }

const ll mx = 1e18;

void solve() {
    int n;
    cin >> n;
    vector<int> a(n);
    for (auto &i : a) cin >> i;

    // precomp
    set<int> s;
    auto upd = [&](int i) {
        if (i != 0 && a[i] > a[i - 1]) s.insert(i);
        else s.erase(i);
    };
    for (int i = 1; i < n; i++) upd(i);

    // solve
    int q;
    cin >> q;
    while (q--) {
        ll i, v, t;
        cin >> i >> v >> t;
        a[--i] = v;
        upd(i);
        if (i < n - 1) upd(i + 1);

        ll ans = max(0LL, t - n + 1);
        ans /= a[0] + 1;
        for (int i : s) {
            ll r = cdiv(a[i], a[i - 1]);
            ans /= r;
            if (ans == 0) break;
        }
        ans *= a.back();
        cout << ans << '\n';
    }
}

signed main() {
    cin.tie(0)->sync_with_stdio(0);
    solve();
}

Danny Mittal's Java code:

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.StringTokenizer;
import java.util.TreeSet;
 
public class MilkBuckets {
 
    public static void main(String[] args) throws IOException {
        BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
        int n = Integer.parseInt(in.readLine());
        long[] sizes = new long[n + 1];
        StringTokenizer tokenizer = new StringTokenizer(in.readLine());
        TreeSet<Integer> increases = new TreeSet<>();
        for (int j = 1; j <= n; j++) {
            sizes[j] = Long.parseLong(tokenizer.nextToken());
            if (j > 1 && sizes[j] > sizes[j - 1]) {
                increases.add(j);
            }
        }
        StringBuilder out = new StringBuilder();
        for (int q = Integer.parseInt(in.readLine()); q > 0; q--) {
            tokenizer = new StringTokenizer(in.readLine());
            int j = Integer.parseInt(tokenizer.nextToken());
            sizes[j] = Long.parseLong(tokenizer.nextToken());
            long t = Long.parseLong(tokenizer.nextToken());
 
            if (j > 1 && sizes[j] > sizes[j - 1]) {
                increases.add(j);
            } else {
                increases.remove(j);
            }
            if (j < n && sizes[j + 1] > sizes[j]) {
                increases.add(j + 1);
            } else {
                increases.remove(j + 1);
            }
 
            long offset = n - 1;
            long cycle = sizes[1] + 1;
            boolean tooLong = false;
            for (int k : increases) {
                long factor = (sizes[k] + sizes[k - 1] - 1) / sizes[k - 1];
                if (1_000_000_000_000_000_000L / cycle < factor) {
                    tooLong = true;
                    break;
                }
                cycle *= factor;
            }
            if (tooLong) {
                out.append(0);
            } else {
                out.append(((t - offset) / cycle) * sizes[n]);
            }
            out.append('\n');
        }
        System.out.print(out);
    }
}

Bonus: solve in $O(N+Q)$!