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;
}
posted @ 2026-10-03 10:14  thenrify  阅读(9)  评论(0)    收藏  举报