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.
Do not simulate bubble sort. For one element, the useful number is how many larger elements stand before it. Bubble sort rounds are hiding an inversion-count statistic, not asking you to actually bubble anything like it is 2009.
For a prefix, is the prefix maximum of values number of previous elements greater than . Every sequence with in 1-based indexing corresponds to exactly one permutation.
Since is nondecreasing, for a fixed , the indices with form an initial segment. So becomes two point conditions: , and if , then .
Only positions , , and matter after switching to 0-based indexing. Only the listed values matter too. Compress both, then DP over which compressed interval contains the current prefix maximum.
For a gap of positions , define as the number of ways to choose all in the gap with every . Then target bucket gets contributions from staying in bucket , or rising from any lower bucket by using .
Bubble Sort Is Just Bait
The first trap is thinking about bubble sort directly. Nope. The array length can be , and we are counting permutations, so simulation is dead on arrival.
Take an element . Let
the number of earlier elements greater than .
During one left-to-right bubble-sort round, this element can move left by at most one position, because after it swaps left once, the scan continues past it. So if there are larger elements before it, it needs at least rounds before all of them cross to its right.
Also, one such larger element crosses it each round until none remain. Therefore the number of bubble-sort rounds for the whole array is
.
For a prefix , define as the number of previous elements greater than . Then
.
So the entire problem becomes counting valid inversion-code sequences , not permutations directly.
Why We May Count Codes Instead Of Permutations
For 1-based positions, . For 0-based positions, which the implementation uses, .
These sequences are in bijection with permutations. Given a permutation, the sequence is obvious. Given the sequence, reconstruct backwards: at position , among the first elements, is the -th smallest remaining value. Pick that from the sorted unused values and remove it. Unique sequence, unique permutation.
So now we count sequences with .
Turning Range Constraints Into Point Constraints
For a fixed , the values are nondecreasing, because each is a prefix maximum. Therefore the set of prefixes with is an initial segment.
Let be the number of prefixes with .
The condition means:
In 0-based positions:
That is the big cleanup. Each restriction only affects at most two checkpoint positions.
Compression
We only care about:
Sort and unique the thresholds:
.
A DP state means the current prefix maximum lies in this bucket:
.
State is only a virtual starting state before we have processed anything.
For every checkpoint position, combine all requirements there:
So each checkpoint allows buckets
.
If that range is empty, the DP naturally becomes zero. No drama.
Counting One Gap
Suppose the previous checkpoint ended before position , and the next checkpoint is . We need to process all for .
For a bound , let be the number of ways to choose the whole gap while keeping every .
At position , without extra restrictions, has choices . With , it has choices. Therefore
.
This can be computed in using factorials and powers:
.
Everything is modulo .
DP Transition
Let be the number of ways before this gap with current maximum in bucket .
To end in bucket after the gap, there are two cases.
First, we were already in bucket . Then the old max already guarantees , so the new gap only has to keep every value . Contribution:
.
Second, we were in some lower bucket. Then the old max was , so the gap must raise the maximum into bucket : all new values must be , but not all of them may be . Contribution from all lower buckets:
.
Use prefix sums of to get this in per state.
After computing the transition for a checkpoint, delete states that violate that checkpoint's combined constraints.
Initialization And Answer
Start with , the virtual empty maximum. After the first gap, only real buckets can be populated.
After processing the final checkpoint , sum all real states.
The complexity is , where and , so effectively per test case. The statement guarantees the total is small enough, so this fits easily. Factorials up to are precomputed once.
#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;
const int MAXN = 1000000;
vector<ll> fact(MAXN + 1), invFact(MAXN + 1);
ll modPow(ll a, ll e) {
a %= MOD;
ll res = 1;
while (e > 0) {
if (e & 1) res = res * a % MOD;
a = a * a % MOD;
e >>= 1;
}
return res;
}
void initFactorials() {
fact[0] = 1;
for (int i = 1; i <= MAXN; ++i) fact[i] = fact[i - 1] * i % MOD;
invFact[MAXN] = modPow(fact[MAXN], MOD - 2);
for (int i = MAXN; i >= 1; --i) invFact[i - 1] = invFact[i] * i % MOD;
}
ll waysBounded(int l, int r, int k) {
if (k < l) return modPow(k + 1, r - l + 1);
if (k >= r) return fact[r + 1] * invFact[l] % MOD;
return fact[k + 1] * invFact[l] % MOD * modPow(k + 1, r - k) % MOD;
}
void solveCase() {
int n, m;
cin >> n >> m;
vector<array<int, 3>> constraints;
vector<int> points{n - 1};
vector<int> bounds{-1, n - 1};
for (int i = 0; i < m; ++i) {
int k, l, r;
cin >> k >> l >> r;
constraints.push_back({k, l, r});
bounds.push_back(k);
points.push_back(l - 1);
if (r < n) points.push_back(r);
}
sort(points.begin(), points.end());
points.erase(unique(points.begin(), points.end()), points.end());
sort(bounds.begin(), bounds.end());
bounds.erase(unique(bounds.begin(), bounds.end()), bounds.end());
int S = (int)bounds.size();
vector<int> atMost(points.size(), S - 1), greaterThan(points.size(), 0);
for (auto [k, l, r] : constraints) {
int id = lower_bound(bounds.begin(), bounds.end(), k) - bounds.begin();
int leftPoint = l - 1;
int p = lower_bound(points.begin(), points.end(), leftPoint) - points.begin();
atMost[p] = min(atMost[p], id);
if (r < n) {
p = lower_bound(points.begin(), points.end(), r) - points.begin();
greaterThan[p] = max(greaterThan[p], id);
}
}
vector<ll> dp(S), ndp(S), pref(S), block(S);
dp[0] = 1;
int start = 0;
for (int idx = 0; idx < (int)points.size(); ++idx) {
int finish = points[idx];
for (int j = 0; j < S; ++j) {
block[j] = waysBounded(start, finish, bounds[j]);
}
pref[0] = dp[0];
for (int j = 1; j < S; ++j) {
pref[j] = (pref[j - 1] + dp[j]) % MOD;
}
fill(ndp.begin(), ndp.end(), 0);
for (int j = 1; j < S; ++j) {
if (j <= greaterThan[idx] || j > atMost[idx]) continue;
ll raiseHere = (block[j] - block[j - 1] + MOD) % MOD;
ndp[j] = (dp[j] * block[j] + pref[j - 1] * raiseHere) % MOD;
}
dp.swap(ndp);
start = finish + 1;
}
ll ans = 0;
for (int j = 1; j < S; ++j) ans = (ans + dp[j]) % MOD;
cout << ans << '\n';
}
int main() {
setIO();
initFactorials();
int T;
cin >> T;
while (T--) solveCase();
return 0;
}#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;
const int MAXN = 1000000;
vector<ll> fact(MAXN + 1), invFact(MAXN + 1);
ll modPow(ll a, ll e) {
a %= MOD;
ll res = 1;
while (e > 0) {
if (e & 1) res = res * a % MOD;
a = a * a % MOD;
e >>= 1;
}
return res;
}
void initFactorials() {
fact[0] = 1;
for (int i = 1; i <= MAXN; ++i) fact[i] = fact[i - 1] * i % MOD;
invFact[MAXN] = modPow(fact[MAXN], MOD - 2);
for (int i = MAXN; i >= 1; --i) invFact[i - 1] = invFact[i] * i % MOD;
}
ll waysBounded(int l, int r, int k) {
if (k < l) return modPow(k + 1, r - l + 1);
if (k >= r) return fact[r + 1] * invFact[l] % MOD;
return fact[k + 1] * invFact[l] % MOD * modPow(k + 1, r - k) % MOD;
}
void solveCase() {
int n, m;
cin >> n >> m;
vector<array<int, 3>> constraints;
vector<int> points{n - 1};
vector<int> bounds{-1, n - 1};
for (int i = 0; i < m; ++i) {
int k, l, r;
cin >> k >> l >> r;
constraints.push_back({k, l, r});
bounds.push_back(k);
points.push_back(l - 1);
if (r < n) points.push_back(r);
}
sort(points.begin(), points.end());
points.erase(unique(points.begin(), points.end()), points.end());
sort(bounds.begin(), bounds.end());
bounds.erase(unique(bounds.begin(), bounds.end()), bounds.end());
int S = (int)bounds.size();
vector<int> atMost(points.size(), S - 1), greaterThan(points.size(), 0);
for (auto [k, l, r] : constraints) {
int id = lower_bound(bounds.begin(), bounds.end(), k) - bounds.begin();
int leftPoint = l - 1;
int p = lower_bound(points.begin(), points.end(), leftPoint) - points.begin();
atMost[p] = min(atMost[p], id);
if (r < n) {
p = lower_bound(points.begin(), points.end(), r) - points.begin();
greaterThan[p] = max(greaterThan[p], id);
}
}
vector<ll> dp(S), ndp(S), pref(S), block(S);
dp[0] = 1;
int start = 0;
for (int idx = 0; idx < (int)points.size(); ++idx) {
int finish = points[idx];
for (int j = 0; j < S; ++j) {
block[j] = waysBounded(start, finish, bounds[j]);
}
pref[0] = dp[0];
for (int j = 1; j < S; ++j) {
pref[j] = (pref[j - 1] + dp[j]) % MOD;
}
fill(ndp.begin(), ndp.end(), 0);
for (int j = 1; j < S; ++j) {
if (j <= greaterThan[idx] || j > atMost[idx]) continue;
ll raiseHere = (block[j] - block[j - 1] + MOD) % MOD;
ndp[j] = (dp[j] * block[j] + pref[j - 1] * raiseHere) % MOD;
}
dp.swap(ndp);
start = finish + 1;
}
ll ans = 0;
for (int j = 1; j < S; ++j) ans = (ans + dp[j]) % MOD;
cout << ans << '\n';
}
int main() {
setIO();
initFactorials();
int T;
cin >> T;
while (T--) solveCase();
return 0;
}