Official

G - Divide a Sequence Editorial by en_translator


Let us define \(\text{dp}_i\) as the answer for the first \(i\) terms of \(A\), \(A'=(A_1,A_2,\ldots,A_i)\). Then the following relation holds (where \(\text{dp}_0=1\)).

  • \(\text{dp}_i=\sum_{j=0}^{i-1} \text{dp}_j \times (\max(\{A_{j+1},A_{j+2},\ldots,A_i\})-\min(\{A_{j+1},A_{j+2},\ldots,A_i\}))\)

The complexity of such DP (Dynamic Programming) costs \(O(N^2)\) when computed naively, but we can reduce it to \(O(N)\) with some tweaks.

First, let us divide the recurrence relation above into \(\max\) and \(\min\).

  • \(\text{dp}_i=\sum_{j=0}^{i-1} \text{dp}_j \times \max(\{A_{j+1},A_{j+2},\ldots,A_i\}) - \sum_{j=0}^{i-1} \text{dp}_j \times \min(\{A_{j+1},A_{j+2},\ldots,A_i\})\)

Such form of transition, namely the form “\(\sum_{j=0}^{i-1} \text{dp}_j \times \max(\{A_{j+1},A_{j+2},\ldots,A_i\})\)” is very suited for optimizations. We may use a data structure like a stack to update \(\max(\{A_{j+1},A_{j+2},\ldots,A_i\})\) accordingly while computing the cumulative sums, so that the \(\text{dp}\) can be computed in a linear time. The \(\min\) part is almost the same.

Specifically, load a pair of the following two values, and update the differences properly.

  • The value of \(\max(\{A_{j+1},A_{j+2},\ldots,A_i\})\)
  • The sum of \(\text{dp}_k\) for all \(k\) such that the value of \(\max(\{A_{k+1},A_{k+2},\ldots,A_i\})\) is equal to the value described above

Sample code (C++)

#include<bits/stdc++.h>

using std::cin;
using std::cout;
using std::endl;
using std::vector;
using ll = long long;

const ll MOD = 998244353;

int main(){
	int N; cin >> N;
	vector<ll> A(N);
	for(int i=0; i<N; i++) cin >> A[i];
	vector<ll> dp(N+1);
	dp[0] = 1;
	std::stack<ll> max, min, max_v, min_v;
	ll max_sum = 0, min_sum = 0;
	for(int i=0; i<N; i++){
		// max
		{
			ll sum_v = dp[i];
			while(!max.empty() && max.top() < A[i]){
				max_sum -= max_v.top()*max.top();
				sum_v += max_v.top();
				max_sum %= MOD;
				sum_v %= MOD;
				max.pop();
				max_v.pop();
			}
			max_sum += sum_v*A[i];
			max_sum %= MOD;
			max.push(A[i]);
			max_v.push(sum_v);
		}
		// min
		{
			ll sum_v = dp[i];
			while(!min.empty() && min.top() > A[i]){
				min_sum -= min_v.top()*min.top();
				sum_v += min_v.top();
				min_sum %= MOD;
				sum_v %= MOD;
				min.pop();
				min_v.pop();
			}
			min_sum += sum_v*A[i];
			min_sum %= MOD;
			min.push(A[i]);
			min_v.push(sum_v);
		}
		dp[i+1] = max_sum-min_sum;
		dp[i+1] %= MOD;
	}
	ll ans = dp[N];
	if(ans < 0) ans += MOD;
	cout << ans << endl;
}

Note that there are alternative way to optimize \(\text{dp}\) utilizing the structure of Cartesian Tree (See also https://atcoder.jp/contests/arc115/tasks/arc115_e )

posted:
last update: