Horses
Given arrays X[0..n - 1] and Y[0..n - 1] with X[i] 1, the profit from selling at year i is P(i) = Y[i] _ j=0 ^ i X[j]. Find _ 0 i < n P(i) 10^9 + 7, with point-update operations on X and Y.
Problem Statement
Rendered from the "Problem Summary" section in the LaTeX write-up.
Given arrays $X[0..n{-}1]$ and $Y[0..n{-}1]$ with $X[i] \ge 1$, the profit from selling at year $i$ is \[ P(i) = Y[i] \cdot \prod_{j=0}^{i} X[j]. \] Find $\max_{0 \le i < n} P(i) \pmod{10^9 + 7}$, with point-update operations on $X$ and $Y$.
Editorial
The solution write-up is rendered from the LaTeX source, with equations kept live through MathJax.
Solution
Candidate Pruning
Lemma.
Only indices $i$ where $X[i] > 1$ (plus index 0) can be ``transition points'' where the optimal selling year changes. Between two consecutive positions with $X > 1$, the prefix product is constant, so the best year in that interval is the one with maximum $Y$.
Since each $X[i] \ge 2$ at a candidate position, the prefix product at least doubles at each candidate. After about 60 candidates, the product exceeds $10^{18}$ and dominates any $Y$ value ($Y \le 10^9$). Therefore, only the \textbf{last $\sim$60 candidates} matter.
Data Structures
A \textbf{segment tree on $X$} supporting product queries (both modular for the answer and logarithmic for comparison).
A \textbf{segment tree on $Y$} supporting range-max queries.
A set of positions where $X[i] > 1$.
For each query, scan the last $\le 64$ candidates, find the best $Y$ in each inter-candidate interval via the $Y$-segment-tree, compare using log-products, and compute the modular answer for the winner.
C++ Implementation
#include <bits/stdc++.h>
using namespace std;
const int MOD = 1e9 + 7;
const int MAXN = 500005;
long long pw(long long b, long long e, long long m) {
long long r = 1; b %= m;
while (e > 0) { if (e & 1) r = r * b % m; b = b * b % m; e >>= 1; }
return r;
}
int n;
long long X[MAXN], Y[MAXN];
long long prodMod[4*MAXN]; double prodLog[4*MAXN];
long long maxY[4*MAXN]; int maxYIdx[4*MAXN];
set<int> candidates;
void buildP(int nd, int l, int r) {
if (l == r) { prodMod[nd]=X[l]%MOD; prodLog[nd]=log((double)X[l]); return; }
int m=(l+r)/2; buildP(2*nd,l,m); buildP(2*nd+1,m+1,r);
prodMod[nd]=prodMod[2*nd]*prodMod[2*nd+1]%MOD;
prodLog[nd]=prodLog[2*nd]+prodLog[2*nd+1];
}
void updP(int nd,int l,int r,int p){
if(l==r){prodMod[nd]=X[p]%MOD;prodLog[nd]=log((double)X[p]);return;}
int m=(l+r)/2;
if(p<=m)updP(2*nd,l,m,p);else updP(2*nd+1,m+1,r,p);
prodMod[nd]=prodMod[2*nd]*prodMod[2*nd+1]%MOD;
prodLog[nd]=prodLog[2*nd]+prodLog[2*nd+1];
}
long long qPM(int nd,int l,int r,int ql,int qr){
if(qr<l||r<ql)return 1; if(ql<=l&&r<=qr)return prodMod[nd];
int m=(l+r)/2;
return qPM(2*nd,l,m,ql,qr)*qPM(2*nd+1,m+1,r,ql,qr)%MOD;
}
double qPL(int nd,int l,int r,int ql,int qr){
if(qr<l||r<ql)return 0; if(ql<=l&&r<=qr)return prodLog[nd];
int m=(l+r)/2;
return qPL(2*nd,l,m,ql,qr)+qPL(2*nd+1,m+1,r,ql,qr);
}
void buildY(int nd,int l,int r){
if(l==r){maxY[nd]=Y[l];maxYIdx[nd]=l;return;}
int m=(l+r)/2;buildY(2*nd,l,m);buildY(2*nd+1,m+1,r);
if(maxY[2*nd]>=maxY[2*nd+1]){maxY[nd]=maxY[2*nd];maxYIdx[nd]=maxYIdx[2*nd];}
else{maxY[nd]=maxY[2*nd+1];maxYIdx[nd]=maxYIdx[2*nd+1];}
}
void updY(int nd,int l,int r,int p){
if(l==r){maxY[nd]=Y[p];maxYIdx[nd]=p;return;}
int m=(l+r)/2;
if(p<=m)updY(2*nd,l,m,p);else updY(2*nd+1,m+1,r,p);
if(maxY[2*nd]>=maxY[2*nd+1]){maxY[nd]=maxY[2*nd];maxYIdx[nd]=maxYIdx[2*nd];}
else{maxY[nd]=maxY[2*nd+1];maxYIdx[nd]=maxYIdx[2*nd+1];}
}
pair<long long,int> qMY(int nd,int l,int r,int ql,int qr){
if(qr<l||r<ql)return{-1,-1}; if(ql<=l&&r<=qr)return{maxY[nd],maxYIdx[nd]};
int m=(l+r)/2;
auto L=qMY(2*nd,l,m,ql,qr),R=qMY(2*nd+1,m+1,r,ql,qr);
return L.first>=R.first?L:R;
}
int solve() {
vector<int> cands;
cands.push_back(0);
for (int x : candidates) if (x > 0) cands.push_back(x);
int start = max(0, (int)cands.size() - 64);
int bestIdx = -1; double bestLog = -1e18;
for (int ci = start; ci < (int)cands.size(); ci++) {
int lo = cands[ci];
int hi = (ci+1 < (int)cands.size()) ? cands[ci+1]-1 : n-1;
auto [yv, yi] = qMY(1, 0, n-1, lo, hi);
double lv = log((double)yv) + qPL(1, 0, n-1, 0, yi);
if (lv > bestLog) { bestLog = lv; bestIdx = yi; }
}
if (start > 0) {
int hi = cands[start] - 1;
auto [yv, yi] = qMY(1, 0, n-1, 0, hi);
double lv = log((double)yv) + qPL(1, 0, n-1, 0, yi);
if (lv > bestLog) { bestLog = lv; bestIdx = yi; }
}
long long ans = Y[bestIdx] % MOD;
ans = ans * qPM(1, 0, n-1, 0, bestIdx) % MOD;
return (int)ans;
}
int init(int N, int X_[], int Y_[]) {
n = N;
for (int i = 0; i < n; i++) { X[i] = X_[i]; Y[i] = Y_[i]; }
buildP(1, 0, n-1); buildY(1, 0, n-1);
candidates.clear();
for (int i = 0; i < n; i++) if (X[i] > 1) candidates.insert(i);
return solve();
}
int updateX(int pos, int val) {
if (X[pos] > 1) candidates.erase(pos);
X[pos] = val;
if (X[pos] > 1) candidates.insert(pos);
updP(1, 0, n-1, pos);
return solve();
}
int updateY(int pos, int val) {
Y[pos] = val;
updY(1, 0, n-1, pos);
return solve();
}
Complexity Analysis
Time per query: $O(\log^2 n)$. At most 64 candidates are examined, each requiring $O(\log n)$ segment-tree queries.
Initialisation: $O(n)$.
Space: $O(n)$.
Code
C++ solution used for this page.
#include <bits/stdc++.h>
using namespace std;
const int MOD = 1e9 + 7;
const int MAXN = 500005;
long long power(long long base, long long exp, long long mod) {
long long result = 1;
base %= mod;
while (exp > 0) {
if (exp & 1) result = result * base % mod;
base = base * base % mod;
exp >>= 1;
}
return result;
}
int n;
long long X[MAXN], Y[MAXN];
// Segment tree: each node stores product of X (mod) and log of product
// and the best selling index in its range
struct Node {
long long prodMod; // product of X in range, mod
double logProd; // log of product of X in range
int bestIdx; // best selling index in range
double bestLogVal; // log(Y[bestIdx]) + log(product X[0..bestIdx])
// relative to this segment: log(Y[bestIdx]) + logProd(0..bestIdx within segment)
};
// Actually simpler: just use segment tree to maintain product of X,
// and a set of candidate indices where X[i] > 1.
// Even simpler approach: segment tree on Y * prefix_product.
// But prefix_product changes when X changes.
// Let's use the "scan from right" approach with a segment tree for products.
// Segment tree for products of X (modular and log)
long long prodMod[4 * MAXN];
double prodLog[4 * MAXN];
void build(int node, int l, int r) {
if (l == r) {
prodMod[node] = X[l] % MOD;
prodLog[node] = log((double)X[l]);
return;
}
int mid = (l + r) / 2;
build(2*node, l, mid);
build(2*node+1, mid+1, r);
prodMod[node] = prodMod[2*node] * prodMod[2*node+1] % MOD;
prodLog[node] = prodLog[2*node] + prodLog[2*node+1];
}
void updateX(int node, int l, int r, int pos) {
if (l == r) {
prodMod[node] = X[pos] % MOD;
prodLog[node] = log((double)X[pos]);
return;
}
int mid = (l + r) / 2;
if (pos <= mid) updateX(2*node, l, mid, pos);
else updateX(2*node+1, mid+1, r, pos);
prodMod[node] = prodMod[2*node] * prodMod[2*node+1] % MOD;
prodLog[node] = prodLog[2*node] + prodLog[2*node+1];
}
long long queryProdMod(int node, int l, int r, int ql, int qr) {
if (qr < l || r < ql) return 1;
if (ql <= l && r <= qr) return prodMod[node];
int mid = (l + r) / 2;
return queryProdMod(2*node, l, mid, ql, qr) *
queryProdMod(2*node+1, mid+1, r, ql, qr) % MOD;
}
double queryProdLog(int node, int l, int r, int ql, int qr) {
if (qr < l || r < ql) return 0;
if (ql <= l && r <= qr) return prodLog[node];
int mid = (l + r) / 2;
return queryProdLog(2*node, l, mid, ql, qr) +
queryProdLog(2*node+1, mid+1, r, ql, qr);
}
// Maintain a set of positions where X[i] > 1
set<int> candidates;
// Segment tree for max Y
long long maxY[4 * MAXN];
int maxYIdx[4 * MAXN];
void buildY(int node, int l, int r) {
if (l == r) {
maxY[node] = Y[l];
maxYIdx[node] = l;
return;
}
int mid = (l + r) / 2;
buildY(2*node, l, mid);
buildY(2*node+1, mid+1, r);
if (maxY[2*node] >= maxY[2*node+1]) {
maxY[node] = maxY[2*node];
maxYIdx[node] = maxYIdx[2*node];
} else {
maxY[node] = maxY[2*node+1];
maxYIdx[node] = maxYIdx[2*node+1];
}
}
void updateY(int node, int l, int r, int pos) {
if (l == r) {
maxY[node] = Y[pos];
maxYIdx[node] = pos;
return;
}
int mid = (l + r) / 2;
if (pos <= mid) updateY(2*node, l, mid, pos);
else updateY(2*node+1, mid+1, r, pos);
if (maxY[2*node] >= maxY[2*node+1]) {
maxY[node] = maxY[2*node];
maxYIdx[node] = maxYIdx[2*node];
} else {
maxY[node] = maxY[2*node+1];
maxYIdx[node] = maxYIdx[2*node+1];
}
}
pair<long long, int> queryMaxY(int node, int l, int r, int ql, int qr) {
if (qr < l || r < ql) return {-1, -1};
if (ql <= l && r <= qr) return {maxY[node], maxYIdx[node]};
int mid = (l + r) / 2;
auto left = queryMaxY(2*node, l, mid, ql, qr);
auto right = queryMaxY(2*node+1, mid+1, r, ql, qr);
return left.first >= right.first ? left : right;
}
int solve() {
// Candidates: positions where X[i] > 1, plus boundary at n-1
// Scan from right. Between consecutive candidates, the best is max Y
// in that interval. Then compare candidates using log of prefix products.
vector<int> cands;
// Add position 0 always as a candidate boundary
cands.push_back(0);
for (int x : candidates) {
if (x > 0) cands.push_back(x);
}
// We consider intervals: [0, cands[0]], [cands[0]+1, cands[1]], etc.
// Actually, the candidates partition the array. Between two consecutive
// candidates (where X > 1), all X values are 1, so prefix product is constant.
// In that interval, the best is just max Y.
// Scan from right, at most ~60 candidates matter (product > 10^18)
// Take the last ~60 candidates
int start = max(0, (int)cands.size() - 64);
int bestIdx = -1;
double bestLogVal = -1e18;
for (int ci = start; ci < (int)cands.size(); ci++) {
int lo = cands[ci];
int hi = (ci + 1 < (int)cands.size()) ? cands[ci + 1] - 1 : n - 1;
auto [yval, yidx] = queryMaxY(1, 0, n - 1, lo, hi);
// Value at yidx: Y[yidx] * prod(X[0..yidx])
double logVal = log((double)yval) + queryProdLog(1, 0, n - 1, 0, yidx);
if (logVal > bestLogVal) {
bestLogVal = logVal;
bestIdx = yidx;
}
}
// If start > 0, also consider the interval [0, cands[start]-1]
// where the product is enormous, so the best in that interval
// might be better. But since products grow exponentially,
// the rightmost candidate with large product dominates.
// Actually, we need the interval [0, cands[start]-1] too.
if (start > 0) {
int hi = cands[start] - 1;
auto [yval, yidx] = queryMaxY(1, 0, n - 1, 0, hi);
double logVal = log((double)yval) + queryProdLog(1, 0, n - 1, 0, yidx);
if (logVal > bestLogVal) {
bestLogVal = logVal;
bestIdx = yidx;
}
}
// Compute answer modulo MOD
long long ans = Y[bestIdx] % MOD;
ans = ans * queryProdMod(1, 0, n - 1, 0, bestIdx) % MOD;
return (int)ans;
}
int init(int N, int X_[], int Y_[]) {
n = N;
for (int i = 0; i < n; i++) { X[i] = X_[i]; Y[i] = Y_[i]; }
build(1, 0, n - 1);
buildY(1, 0, n - 1);
candidates.clear();
for (int i = 0; i < n; i++) {
if (X[i] > 1) candidates.insert(i);
}
return solve();
}
int updateX(int pos, int val) {
if (X[pos] > 1) candidates.erase(pos);
X[pos] = val;
if (X[pos] > 1) candidates.insert(pos);
updateX(1, 0, n - 1, pos);
return solve();
}
int updateY(int pos, int val) {
Y[pos] = val;
updateY(1, 0, n - 1, pos);
return solve();
}
int main() {
int N;
scanf("%d", &N);
int Xa[N], Ya[N];
for (int i = 0; i < N; i++) scanf("%d", &Xa[i]);
for (int i = 0; i < N; i++) scanf("%d", &Ya[i]);
printf("%d\n", init(N, Xa, Ya));
int M;
scanf("%d", &M);
for (int i = 0; i < M; i++) {
int type, pos, val;
scanf("%d %d %d", &type, &pos, &val);
if (type == 1) printf("%d\n", updateX(pos, val));
else printf("%d\n", updateY(pos, val));
}
return 0;
}
Source Files and Assets
Raw files are still available here when you want the original TeX, C++, or statement assets.