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.
Separate the annoying probability part from the combinatorics. Try writing the answer as “probability all unchosen segments do not exist” times “extra multiplier for the chosen segments.”
For a segment with existence probability , the absent probability is . If you factor out the absent probability of every segment, then choosing this segment contributes
After that factoring, the problem becomes: find the weighted sum of all subsets of segments that tile cells exactly once. “Exactly once” means no gaps and no overlaps, so the chosen segments must form a chain of intervals.
Let be the total weight of ways to exactly cover prefix . If the last chosen segment is , then everything before it must exactly cover , so it contributes
Group segments by right endpoint. Initialize , and for each segment ending at , do
The final answer is
We need the probability that the random set of existing segments covers every cell exactly once.
The direct probability sum over all subsets looks nasty:
where “valid” means the selected segments cover every cell exactly once.
That formula is correct, but computing it subset-by-subset is obviously dead on arrival. is not a vibe.
Factor out all absent probabilities
For segment , define
Its absent probability is
Now factor the absent probability out of every segment, no matter whether it is chosen or not:
If segment is not chosen, this factor already accounts for it.
If segment is chosen, we need to replace by , so the chosen segment contributes an extra multiplier
Call this value .
So the answer becomes:
Now the probability bookkeeping is mostly gone. We just need a weighted count of valid tilings.
What does a valid subset look like?
If every cell is covered exactly once, then the chosen segments cannot overlap, and they cannot leave gaps.
So the chosen segments form a perfect chain covering:
For example, a valid chain might look like:
The important thing: if the last chosen segment is , then everything before it must exactly cover cells .
That screams prefix DP.
DP definition
Let
The empty prefix has one way:
Now take a segment with weight .
If we use it as the last segment of a cover ending at , then cells before it must already be exactly covered up to .
So this segment contributes:
to .
Therefore:
That is the whole trick. No interval graph madness, no inclusion-exclusion hell, just factoring probabilities so the remaining structure becomes a clean tiling DP.
Why this avoids double counting
Every valid selected subset has a unique last segment: the segment covering cell in the full problem, or covering cell for prefix .
Remove that last segment , and what remains is a valid exact cover of .
So every valid subset is counted exactly once by the transition that appends its final segment. Conversely, every DP transition appends a segment directly after an exact prefix cover, so it creates no gaps and no overlaps.
That gives a bijection. Fancy word, simple idea: nothing is lost, nothing is duplicated.
Modulo details
We work modulo
Fractions are represented with modular inverses:
Since , both and are nonzero modulo , so inverses exist.
For each segment:
Then process positions from to , applying all segments that end there.
Finally:
Complexity
Each segment creates one transition, and each transition is processed once.
Time complexity:
Using fast exponentiation for inverses, this is still easily fine for .
Memory complexity:
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
void setIO() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
}
const ll MOD = 998244353;
ll mod_pow(ll a, ll e) {
ll res = 1;
while (e > 0) {
if (e & 1) res = res * a % MOD;
a = a * a % MOD;
e >>= 1;
}
return res;
}
ll inv(ll x) {
return mod_pow(x, MOD - 2);
}
int main() {
setIO();
int n, m;
cin >> n >> m;
vector<vector<pair<int, ll>>> by_end(m + 1);
ll base = 1;
for (int i = 0; i < n; i++) {
int l, r;
ll p, q;
cin >> l >> r >> p >> q;
ll absent = (q - p) % MOD * inv(q) % MOD;
ll weight = p % MOD * inv(q - p) % MOD;
base = base * absent % MOD;
by_end[r].push_back({l, weight});
}
vector<ll> dp(m + 1, 0);
dp[0] = 1;
for (int r = 1; r <= m; r++) {
for (auto [l, weight] : by_end[r]) {
dp[r] = (dp[r] + dp[l - 1] * weight) % MOD;
}
}
cout << dp[m] * base % MOD << '\n';
return 0;
}#include <bits/stdc++.h>
using namespace std;
using ll = long long;
void setIO() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
}
const ll MOD = 998244353;
ll mod_pow(ll a, ll e) {
ll res = 1;
while (e > 0) {
if (e & 1) res = res * a % MOD;
a = a * a % MOD;
e >>= 1;
}
return res;
}
ll inv(ll x) {
return mod_pow(x, MOD - 2);
}
int main() {
setIO();
int n, m;
cin >> n >> m;
vector<vector<pair<int, ll>>> by_end(m + 1);
ll base = 1;
for (int i = 0; i < n; i++) {
int l, r;
ll p, q;
cin >> l >> r >> p >> q;
ll absent = (q - p) % MOD * inv(q) % MOD;
ll weight = p % MOD * inv(q - p) % MOD;
base = base * absent % MOD;
by_end[r].push_back({l, weight});
}
vector<ll> dp(m + 1, 0);
dp[0] = 1;
for (int r = 1; r <= m; r++) {
for (auto [l, weight] : by_end[r]) {
dp[r] = (dp[r] + dp[l - 1] * weight) % MOD;
}
}
cout << dp[m] * base % MOD << '\n';
return 0;
}