Strongly Connected 2
Strongly Connected 2
线段树
1. 问题转化
原问题:
给定 N 个顶点,基础边为 i+1 -> i(即链 N → N-1 → ... → 1)。另有 M 条额外边 (X_i, Y_i)。
从 M 条边中选择若干条保留,问有多少种选择方案使得最终图强连通。
关键观察:
因为固有链边使所有点都能回到 1(k → k-1 → ... → 1),所以整个图强连通 当且仅当 从顶点 1 出发能到达顶点 N。
因此问题等价于:从 M 条边中选择子集,使得 仅使用选中的边 + 固有链边,1 能到达 N。
2. 状态定义
将所有额外边按起点 X 从小到大排序。
令 dp[i][r] 表示:
处理完前 i 条排序后的边后,从顶点 1 出发当前可达的最大顶点编号恰好为 r 的方案数。
- 下标范围:
1 ≤ r ≤ N。 - 初始状态:
dp[0][1] = 1,其余为 0。 - 目标答案:
dp[M][N]。
3. 转移方程(分段函数)
考虑第 i 条边 (X, Y)(已排序)。
从旧状态 dp_old 更新到新状态 dp_new。
3.1 若 Y ≤ X(反向边或自环)
这条边不会扩大可达范围,选或不选都保持原最大可达不变,所以:
[
dp_{\text{new}}[r] = 2 \cdot dp_{\text{old}}[r], \quad 1 \le r \le N
]
3.2 若 Y > X(正向扩展边)
此时按当前最大可达 r 与 X, Y 的关系分类:
[
dp_{\text{new}}[r] =
\begin{cases}
2 \cdot dp_{\text{old}}[r], & r < X \quad \text{(起点不可达,选/不选均无影响)} \[6pt]
1 \cdot dp_{\text{old}}[r], & X \le r < Y \quad \text{(只能不选,否则会跳到 Y)} \[6pt]
dp_{\text{old}}[Y] + \sum\limits_{k=X}^{Y} dp_{\text{old}}[k], & r = Y \quad \text{(不选保留 + 从区间内任意状态选边跳过来)} \[6pt]
2 \cdot dp_{\text{old}}[r], & r > Y \quad \text{(Y 早已可达,选/不选均无影响)}
\end{cases}
]
注意:当
r = Y时,求和∑_{k=X}^{Y} dp_old[k]中包含了dp_old[Y],所以点加后的新值 = 旧值 + 区间和,恰好等于“不选” + “选”的总方案数。
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
const int MOD = 998244353;
class SegTree {
int n;
vector<ll> sum, mul, add;
void apply_mul(int node, int l, int r, int k) {
sum[node] = sum[node] * k % MOD;
mul[node] = mul[node] * k % MOD;
add[node] = add[node] * k % MOD;
}
void apply_add(int node, int l, int r, int k) {
sum[node] = (sum[node] + (r - l + 1) * 1LL * k) % MOD;
add[node] = (add[node] + k) % MOD;
}
void push_down(int node, int l, int r) {
int mid = (l + r) >> 1;
if (mul[node] != 1) {
apply_mul(node << 1, l, mid, mul[node]);
apply_mul(node << 1 | 1, mid + 1, r, mul[node]);
mul[node] = 1;
}
if (add[node] != 0) {
apply_add(node << 1, l, mid, add[node]);
apply_add(node << 1 | 1, mid + 1, r, add[node]);
add[node] = 0;
}
}
public:
SegTree(int _n) : n(_n) {
sum.resize(4 * n, 0);
mul.resize(4 * n, 1);
add.resize(4 * n, 0);
}
void range_mul(int ql, int qr, int k) {
if (ql > qr) return;
function<void(int,int,int)> update = [&](int node, int l, int r) {
if (ql <= l && r <= qr) {
apply_mul(node, l, r, k);
return;
}
push_down(node, l, r);
int mid = (l + r) >> 1;
if (ql <= mid) update(node << 1, l, mid);
if (qr > mid) update(node << 1 | 1, mid + 1, r);
sum[node] = (sum[node << 1] + sum[node << 1 | 1]) % MOD;
};
update(1, 1, n);
}
void point_add(int pos, int k) {
function<void(int,int,int)> update = [&](int node, int l, int r) {
if (l == r) {
sum[node] = (sum[node] + k) % MOD;
return;
}
push_down(node, l, r);
int mid = (l + r) >> 1;
if (pos <= mid) update(node << 1, l, mid);
else update(node << 1 | 1, mid + 1, r);
sum[node] = (sum[node << 1] + sum[node << 1 | 1]) % MOD;
};
update(1, 1, n);
}
ll range_sum(int ql, int qr) {
if (ql > qr) return 0;
function<ll(int,int,int)> query = [&](int node, int l, int r) {
if (ql <= l && r <= qr) return sum[node];
push_down(node, l, r);
int mid = (l + r) >> 1;
ll res = 0;
if (ql <= mid) res += query(node << 1, l, mid);
if (qr > mid) res += query(node << 1 | 1, mid + 1, r);
return res % MOD;
};
return query(1, 1, n);
}
ll point_query(int pos) {
return range_sum(pos, pos);
}
};
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int N, M;
cin >> N >> M;
vector<pair<int, int>> edges(M);
for (int i = 0; i < M; ++i) {
cin >> edges[i].first >> edges[i].second;
}
sort(edges.begin(), edges.end()); // 按 X 升序
SegTree seg(N);
seg.point_add(1, 1); // dp[1] = 1
for (auto [x, y] : edges) {
if (y <= x) {
// 这条边不会扩大可达范围,所有方案数乘2
seg.range_mul(1, N, 2);
} else {
// y > x
ll s = seg.range_sum(x, y);
seg.point_add(y, s % MOD);
seg.range_mul(1, x - 1, 2);
seg.range_mul(y + 1, N, 2);
}
}
ll ans = seg.point_query(N);
cout << ans % MOD << '\n';
return 0;
}

浙公网安备 33010602011771号