Search for a command to run...
Progressive hints first, then the full explanation and implementation when you're ready to cash out.
Review status
AI-generated and still unreviewed. Double-check the details before internalizing them.
Hints
Open only as much as you need to keep the solve alive.
Think of as the sequence of record highs while scanning left to right. Same for after reversing the array. So first solve one-sided: count subsequences whose left-visible heights equal some fixed increasing list .
For the one-sided problem, use DP over how many required visible heights have already appeared. State means the subsequence currently has left view and no illegal visible value.
When processing value , let be the first index with . If the DP state is already past , then is hidden below the current maximum, so choosing/skipping it doubles those states. If , it can also advance state to state .
A full valid subsequence may contain multiple global maximums. The first chosen global maximum is what completes the left view; the last chosen global maximum is what completes the right view. Everything strictly between those two chosen maximums is free.
Compute : ways for position to be the first chosen global maximum completing . Compute similarly on the reversed array for the last chosen global maximum completing . Sweep maximum positions left to right and maintain a doubled running sum for the free middle. That’s the whole trick.
Let’s strip the problem down to what it actually cares about.
A tower is visible from the left exactly when it becomes a new prefix maximum. So is the increasing list of record heights in . Looking from the right is the same thing after reversing the array.
We need count subsequences that preserve both skylines.
The hard part is that deleting elements can make some previously hidden value become visible. Example: in , the is hidden by . If you delete , suddenly becomes visible. So this is not just “keep all record highs and choose whatever else”. That assumption dies instantly.
One-sided DP
First solve this helper problem:
Given an array and an increasing target list , count, for every position where , the number of ways to choose a subsequence ending at such that:
Call this array calc(a, b).
Use DP states .
State means: after processing some prefix, we have chosen a subsequence whose visible-from-left list is exactly
Initially for the empty subsequence.
Now process a value .
Let be the first index such that .
There are two kinds of valid choices:
If is not in , it can never be chosen as a new visible height. It can only be chosen when it is hidden by an already chosen larger/equal visible height.
Using zero-based states, this becomes:
lf = lower_bound(b, x)[lf+1, m] by ,b[lf] == x, add state lf into state lf+1.When is the global maximum , before processing it, state is exactly the number of ways to build the whole left skyline except the maximum. Choosing this position as the first maximum completes the left skyline, so res[i] = dp[m-1].
We need range multiply by and point get/add. A lazy segment tree does that in per element.
Combining left and right
Compute:
dpL[i] from the original array and ,dpR[i] from the reversed array and , then reverse it back.For a valid subsequence, let:
Then .
The part up to must form the left skyline, counted by dpL[p].
The part from to the end must form the right skyline, counted by dpR[q].
Everything strictly between and is free, because once a global maximum is already chosen on both sides, no middle element can create a new visible height. Even another maximum is harmless: equal height is not strictly higher.
So for every pair of maximum positions with , we need:
when , and
when .
Sweep from left to right. Maintain
At maximum position , add:
After each position, double because this position becomes one more optional middle element for future right endpoints. If the current position is a maximum, also add dpL[i] as a new possible first maximum.
That’s it. Four seconds is plenty; no need to summon dark magic, just a segment tree and some discipline.
Complexity: per test case, with total .
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
void setIO() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
}
const int MOD = 998244353;
int addmod(int a, int b) {
a += b;
if (a >= MOD) a -= MOD;
return a;
}
int mulmod(int a, int b) {
return int(1LL * a * b % MOD);
}
struct SegTree {
int n;
vector<int> val, lazy;
SegTree(int n = 0) { init(n); }
void init(int n_) {
n = n_;
val.assign(n, 0);
lazy.assign(4 * max(1, n), 1);
}
void apply(int v, int x) {
lazy[v] = mulmod(lazy[v], x);
}
void push(int v) {
if (lazy[v] == 1) return;
apply(v * 2, lazy[v]);
apply(v * 2 + 1, lazy[v]);
lazy[v] = 1;
}
void rangeMul(int v, int l, int r, int ql, int qr, int x) {
if (ql >= qr) return;
if (ql == l && qr == r) {
apply(v, x);
return;
}
push(v);
int mid = (l + r) / 2;
rangeMul(v * 2, l, mid, ql, min(qr, mid), x);
rangeMul(v * 2 + 1, mid, r, max(ql, mid), qr, x);
}
void rangeMul(int l, int r, int x) {
rangeMul(1, 0, n, l, r, x);
}
int get(int v, int l, int r, int pos) {
if (r - l == 1) return mulmod(val[l], lazy[v]);
push(v);
int mid = (l + r) / 2;
if (pos < mid) return get(v * 2, l, mid, pos);
return get(v * 2 + 1, mid, r, pos);
}
int get(int pos) {
return get(1, 0, n, pos);
}
void pointAdd(int v, int l, int r, int pos, int x) {
if (r - l == 1) {
val[l] = addmod(mulmod(val[l], lazy[v]), x);
lazy[v] = 1;
return;
}
push(v);
int mid = (l + r) / 2;
if (pos < mid) pointAdd(v * 2, l, mid, pos, x);
else pointAdd(v * 2 + 1, mid, r, pos, x);
}
void pointAdd(int pos, int x) {
pointAdd(1, 0, n, pos, x);
}
};
vector<ll> visibleFromLeft(const vector<ll>& a) {
vector<ll> res;
ll cur = -1;
for (ll x : a) {
if (x > cur) {
res.push_back(x);
cur = x;
}
}
return res;
}
vector<int> calc(const vector<ll>& a, const vector<ll>& b) {
int n = int(a.size());
int m = int(b.size());
vector<int> res(n, 0);
SegTree st(m + 1);
st.pointAdd(0, 1);
ll mx = b.back();
for (int i = 0; i < n; i++) {
ll x = a[i];
if (x > mx) continue;
if (x == mx) res[i] = st.get(m - 1);
int lf = int(lower_bound(b.begin(), b.end(), x) - b.begin());
st.rangeMul(lf + 1, m + 1, 2);
if (lf < m && b[lf] == x) {
int cur = st.get(lf);
st.pointAdd(lf + 1, cur);
}
}
return res;
}
void solve() {
int n;
cin >> n;
vector<ll> a(n);
for (ll &x : a) cin >> x;
vector<ll> leftView = visibleFromLeft(a);
vector<ll> rev = a;
reverse(rev.begin(), rev.end());
vector<ll> rightView = visibleFromLeft(rev);
vector<int> dpL = calc(a, leftView);
vector<int> dpR = calc(rev, rightView);
reverse(dpR.begin(), dpR.end());
ll mx = *max_element(a.begin(), a.end());
int ans = 0;
int sumLeft = 0;
for (int i = 0; i < n; i++) {
if (a[i] == mx) {
ans = addmod(ans, mulmod(addmod(sumLeft, dpL[i]), dpR[i]));
}
sumLeft = mulmod(sumLeft, 2);
if (a[i] == mx) {
sumLeft = addmod(sumLeft, dpL[i]);
}
}
cout << ans << '\n';
}
int main() {
setIO();
int t;
cin >> t;
while (t--) solve();
}#include <bits/stdc++.h>
using namespace std;
using ll = long long;
void setIO() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
}
const int MOD = 998244353;
int addmod(int a, int b) {
a += b;
if (a >= MOD) a -= MOD;
return a;
}
int mulmod(int a, int b) {
return int(1LL * a * b % MOD);
}
struct SegTree {
int n;
vector<int> val, lazy;
SegTree(int n = 0) { init(n); }
void init(int n_) {
n = n_;
val.assign(n, 0);
lazy.assign(4 * max(1, n), 1);
}
void apply(int v, int x) {
lazy[v] = mulmod(lazy[v], x);
}
void push(int v) {
if (lazy[v] == 1) return;
apply(v * 2, lazy[v]);
apply(v * 2 + 1, lazy[v]);
lazy[v] = 1;
}
void rangeMul(int v, int l, int r, int ql, int qr, int x) {
if (ql >= qr) return;
if (ql == l && qr == r) {
apply(v, x);
return;
}
push(v);
int mid = (l + r) / 2;
rangeMul(v * 2, l, mid, ql, min(qr, mid), x);
rangeMul(v * 2 + 1, mid, r, max(ql, mid), qr, x);
}
void rangeMul(int l, int r, int x) {
rangeMul(1, 0, n, l, r, x);
}
int get(int v, int l, int r, int pos) {
if (r - l == 1) return mulmod(val[l], lazy[v]);
push(v);
int mid = (l + r) / 2;
if (pos < mid) return get(v * 2, l, mid, pos);
return get(v * 2 + 1, mid, r, pos);
}
int get(int pos) {
return get(1, 0, n, pos);
}
void pointAdd(int v, int l, int r, int pos, int x) {
if (r - l == 1) {
val[l] = addmod(mulmod(val[l], lazy[v]), x);
lazy[v] = 1;
return;
}
push(v);
int mid = (l + r) / 2;
if (pos < mid) pointAdd(v * 2, l, mid, pos, x);
else pointAdd(v * 2 + 1, mid, r, pos, x);
}
void pointAdd(int pos, int x) {
pointAdd(1, 0, n, pos, x);
}
};
vector<ll> visibleFromLeft(const vector<ll>& a) {
vector<ll> res;
ll cur = -1;
for (ll x : a) {
if (x > cur) {
res.push_back(x);
cur = x;
}
}
return res;
}
vector<int> calc(const vector<ll>& a, const vector<ll>& b) {
int n = int(a.size());
int m = int(b.size());
vector<int> res(n, 0);
SegTree st(m + 1);
st.pointAdd(0, 1);
ll mx = b.back();
for (int i = 0; i < n; i++) {
ll x = a[i];
if (x > mx) continue;
if (x == mx) res[i] = st.get(m - 1);
int lf = int(lower_bound(b.begin(), b.end(), x) - b.begin());
st.rangeMul(lf + 1, m + 1, 2);
if (lf < m && b[lf] == x) {
int cur = st.get(lf);
st.pointAdd(lf + 1, cur);
}
}
return res;
}
void solve() {
int n;
cin >> n;
vector<ll> a(n);
for (ll &x : a) cin >> x;
vector<ll> leftView = visibleFromLeft(a);
vector<ll> rev = a;
reverse(rev.begin(), rev.end());
vector<ll> rightView = visibleFromLeft(rev);
vector<int> dpL = calc(a, leftView);
vector<int> dpR = calc(rev, rightView);
reverse(dpR.begin(), dpR.end());
ll mx = *max_element(a.begin(), a.end());
int ans = 0;
int sumLeft = 0;
for (int i = 0; i < n; i++) {
if (a[i] == mx) {
ans = addmod(ans, mulmod(addmod(sumLeft, dpL[i]), dpR[i]));
}
sumLeft = mulmod(sumLeft, 2);
if (a[i] == mx) {
sumLeft = addmod(sumLeft, dpL[i]);
}
}
cout << ans << '\n';
}
int main() {
setIO();
int t;
cin >> t;
while (t--) solve();
}