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.
An inversion graph has an edge for every decreasing pair. So if you ever pick three indices with , those three vertices form a triangle. A triangle is the least subtle way for a graph to scream “not bipartite.”
The converse is also true here: an inversion graph is good iff the chosen sequence has no strictly decreasing subsequence of length . You can think of the selected values as being split into two nondecreasing layers.
Equal values are annoying only if you let them be. Replace every by its stable rank: sort by value, breaking ties by index. Then for every , is preserved exactly when the original pair is an inversion.
For a selected prefix, track two numbers: , the maximum selected rank, and , the maximum selected rank that already has a bigger selected value before it. If the next rank is , then either and it becomes the new maximum, or and it becomes the new “second-layer” maximum.
The raw DP over states is cubic if updated naively. Store every DP row and every DP column in Fenwick trees. Then transitions need row prefix sums and column prefix sums , giving .
The key is to stop thinking about colors first. Colors are just the symptom. The actual disease is a decreasing triple.
From Coloring To Decreasing Triples
Take any selected subsequence .
If it contains indices with
then all three pairs are inversions, so the inversion graph contains a triangle. A triangle cannot be 2-colored, so the subsequence is not good.
Now the less obvious direction: if there is no decreasing subsequence of length , then the inversion graph is bipartite.
Classify each selected element by the length of the longest decreasing subsequence ending there. Since length never happens, every selected element has rank either or .
Elements of the same rank cannot form an inversion:
So each rank class is nondecreasing, meaning every inversion goes between the two classes. Color rank red and rank blue. Done.
Therefore:
That is the whole graph part. The graph has left the building.
Compress Values Without Breaking Inversions
The array can have equal values. Inversions are strict, so equal values must not become inversions accidentally.
Replace each by its stable rank among pairs sorted increasingly. Equivalently:
This creates a permutation of .
For every :
Equal original values get increasing ranks by index, so they still do not create inversions. Nice and clean.
From now on, assume the sequence is a permutation.
DP State
Process positions from left to right.
For every good selected subsequence inside the processed prefix, store:
If no such value exists, . The empty subsequence is state .
Why does matter? Because every future selected value below would complete a decreasing triple.
Suppose a previous value already had a bigger value before it. If we now choose , then we have
in increasing index order. That is exactly the forbidden triple. So future chosen values that are not new maxima must stay above .
Transitions
Let the next value be .
We can always skip it.
If we take it, there are two cases.
Case 1: .
Then is bigger than everything selected so far, so it is not preceded by a bigger value. The new state is:
Case 2: .
Then the old maximum is before and bigger than , so is now a value preceded by a bigger one. This is valid only if . The new state is:
If but , taking creates a decreasing triple, so nope.
The naive DP is:
For each , the updates are:
and
These formulas already include the skip transition implicitly, because we never delete old states.
Speeding It Up
There are states. For each element, we need many prefix sums:
Use Fenwick trees:
When we add some value to , update both structures.
For a value :
Important: buffer all updates for the current first, then apply them. Otherwise the same array element could be used twice in one transition, which would be cursed and wrong.
Finally, sum all states. State is included, so the empty subsequence is counted automatically.
Complexity
For each of elements, we do Fenwick queries/updates, each costing .
Memory is , which is fine for .
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
void setIO() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
}
const int MOD = 1000000007;
struct Fenwick2D {
int n;
vector<vector<int>> row, col;
Fenwick2D(int n = 0) { init(n); }
void init(int m) {
n = m;
row.assign(n + 1, vector<int>(n + 2, 0));
col.assign(n + 1, vector<int>(n + 2, 0));
}
void add(int &a, int b) {
a += b;
if (a >= MOD) a -= MOD;
}
void updateRow(int r, int pos, int val) {
for (int i = pos + 1; i <= n + 1; i += i & -i) add(row[r][i], val);
}
void updateCol(int c, int pos, int val) {
for (int i = pos + 1; i <= n + 1; i += i & -i) add(col[c][i], val);
}
int queryRow(int r, int pos) {
int res = 0;
for (int i = pos + 1; i > 0; i -= i & -i) add(res, row[r][i]);
return res;
}
int queryCol(int c, int pos) {
int res = 0;
for (int i = pos + 1; i > 0; i -= i & -i) add(res, col[c][i]);
return res;
}
void updateState(int mx, int second, int val) {
if (val == 0) return;
updateRow(mx, second, val);
updateCol(second, mx, val);
}
};
int main() {
setIO();
int T;
cin >> T;
while (T--) {
int n;
cin >> n;
vector<int> b(n + 1), a(n + 1);
for (int i = 1; i <= n; i++) cin >> b[i];
for (int i = 1; i <= n; i++) {
a[i] = 1;
for (int j = 1; j < i; j++) {
if (b[j] <= b[i]) a[i]++;
}
for (int j = i + 1; j <= n; j++) {
if (b[j] < b[i]) a[i]++;
}
}
Fenwick2D bit(n);
bit.updateState(0, 0, 1);
for (int i = 1; i <= n; i++) {
int x = a[i];
vector<tuple<int, int, int>> pending;
for (int mx = x + 1; mx <= n; mx++) {
int ways = bit.queryRow(mx, x - 1);
pending.emplace_back(mx, x, ways);
}
for (int second = 0; second < x; second++) {
int ways = bit.queryCol(second, x - 1);
pending.emplace_back(x, second, ways);
}
for (auto [mx, second, ways] : pending) {
bit.updateState(mx, second, ways);
}
}
int ans = 0;
for (int mx = 0; mx <= n; mx++) {
ans += bit.queryRow(mx, n);
if (ans >= MOD) ans -= MOD;
}
cout << ans << '\n';
}
}#include <bits/stdc++.h>
using namespace std;
using ll = long long;
void setIO() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
}
const int MOD = 1000000007;
struct Fenwick2D {
int n;
vector<vector<int>> row, col;
Fenwick2D(int n = 0) { init(n); }
void init(int m) {
n = m;
row.assign(n + 1, vector<int>(n + 2, 0));
col.assign(n + 1, vector<int>(n + 2, 0));
}
void add(int &a, int b) {
a += b;
if (a >= MOD) a -= MOD;
}
void updateRow(int r, int pos, int val) {
for (int i = pos + 1; i <= n + 1; i += i & -i) add(row[r][i], val);
}
void updateCol(int c, int pos, int val) {
for (int i = pos + 1; i <= n + 1; i += i & -i) add(col[c][i], val);
}
int queryRow(int r, int pos) {
int res = 0;
for (int i = pos + 1; i > 0; i -= i & -i) add(res, row[r][i]);
return res;
}
int queryCol(int c, int pos) {
int res = 0;
for (int i = pos + 1; i > 0; i -= i & -i) add(res, col[c][i]);
return res;
}
void updateState(int mx, int second, int val) {
if (val == 0) return;
updateRow(mx, second, val);
updateCol(second, mx, val);
}
};
int main() {
setIO();
int T;
cin >> T;
while (T--) {
int n;
cin >> n;
vector<int> b(n + 1), a(n + 1);
for (int i = 1; i <= n; i++) cin >> b[i];
for (int i = 1; i <= n; i++) {
a[i] = 1;
for (int j = 1; j < i; j++) {
if (b[j] <= b[i]) a[i]++;
}
for (int j = i + 1; j <= n; j++) {
if (b[j] < b[i]) a[i]++;
}
}
Fenwick2D bit(n);
bit.updateState(0, 0, 1);
for (int i = 1; i <= n; i++) {
int x = a[i];
vector<tuple<int, int, int>> pending;
for (int mx = x + 1; mx <= n; mx++) {
int ways = bit.queryRow(mx, x - 1);
pending.emplace_back(mx, x, ways);
}
for (int second = 0; second < x; second++) {
int ways = bit.queryCol(second, x - 1);
pending.emplace_back(x, second, ways);
}
for (auto [mx, second, ways] : pending) {
bit.updateState(mx, second, ways);
}
}
int ans = 0;
for (int mx = 0; mx <= n; mx++) {
ans += bit.queryRow(mx, n);
if (ans >= MOD) ans -= MOD;
}
cout << ans << '\n';
}
}