题解:AT_abc450_f [ABC450F] Strongly Connected 2

首先考虑转化问题, 初始存在的边是 \(i + 1 \rarr i\), 然后给定的边是 \(x \rarr y \ (x \lt y)\).

那么我们需要考虑整个图是一个强连通分量的条件是什么,

初始边保证了我们可以从任意大编号的点走到比它编号小的点,

那么我们的核心就是, 只要能够从 \(1\) 点走到最大编号的点 \(n\), 则可以从 \(n\) 走回所有比它编号小的点, 图强连通.

所以得到问题的转化, 从给定区间中选择几个, 使得被选择的区间的并为 \([1, n]\), 求选择方案数.

所以, 我们令 f[i][j] 表示考虑前 \(i\) 条边, 连续覆盖了 \([1, j]\) (即最远可达点为 \(j\)), 此时的方案数.

然后意识到了, \(f\) 需要在能够覆盖的最远点逐渐增大的过程中转移, 所以将边按照右端点从小到大排序.

我们现在考虑转移, 要对当前边 \(e_i = [l, r]\) 做转移:

  • \(j \lt l\), 这个边选不选不影响其最远可达点, \(\displaystyle f_{i, j} = 2 \times f_{i - 1, j}\).

  • \(l \leqslant j \lt r\), 想要保持 \(j\) 不向后, 只有一种可能, 不选 \(i\), \(\displaystyle f_{i, j} = f_{i - 1, j}\).

  • \(j = r\), 之前到达了 \([l, r]\) 的, 都可以通过选择 \(i\) 到达 \(r\), 或之前在 \(r\) 的也可以不选, \(\displaystyle f_{i, j} = \sum_{k = l}^{r} f_{i - 1, k} + f_{i - 1, j}\).

  • \(j \gt r\), 因为排序, 不会出现这种情况, 严谨考虑的话, \(i\) 选不选, 不会影响这里的 \(j\), \(\displaystyle f_{i, j} = 2 \times f_{i - 1, j}\).

所以我们就可以得到下面的暴力.

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <cmath>
using namespace std;

const int NR = 2e5 + 10;
const long long mod = 998244353;

struct node
{
    int l, r;

    bool operator < (const node &b) const
    {
        return r < b.r;
    }
} a[NR];

long long f[1010][1010];

int main()
{
    int n, m;
    scanf("%d%d", &n, &m);
    for (int i = 1; i <= m; i ++)
    {
        scanf("%d%d", &a[i].l, &a[i].r);
    }
    sort(a + 1, a + m + 1);
    f[0][1] = 1;
    for (int i = 1; i <= m; i ++)
    {
        int l = a[i].l, r = a[i].r;
        long long sum = 0;
        for (int j = 1; j <= n; j ++)
        {
            if (j < l || j > r) f[i][j] = f[i - 1][j] * 2 % mod;
            else f[i][j] = f[i - 1][j];
            if (l <= j && j <= r) sum = (sum + f[i - 1][j]) % mod;
            if (j == r) f[i][j] = (f[i - 1][j] + sum) % mod;
        }
    }
    long long ans = f[m][n];
    printf("%lld\n", ans);
    return 0;
}

下一步, 我们再去考虑优化, 我们发现, 每次的转移是在相邻两个第一维之间发生的, 即 \(i - 1 \rarr i\) 的转移.

然后对于 \(j \isin [1, l) \cup (r, n]\), 做 \(f_{i - 1, j} \xrightarrow{\times 2} f_{i, j}\), 这是区间乘法.

对于 \(j \isin [l, r)\), 有 \(f_{i - 1, j} \rarr f_{i, j}\), 不变.

对于 \(j = r\), 是 \(\displaystyle \sum_{k = l}^{r} f_{i - 1, k} + f_{i - 1, j} \rarr f_{i, j}\), 这个是区间求和然后单点加.

所以通过上面的分析, 我们可以使用线段树优化整体转移的方法,

用线段树维护 \(j\) 这维的值, 对每层在线段树上 \(O(\log{n})\) 实现转移.

总复杂度 \(O(n \log{n})\).

\(Code\)

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <cmath>
using namespace std;

const int NR = 2e5 + 10;
const long long mod = 998244353;

struct node
{
    int l, r;

    bool operator < (const node &b) const
    {
        return r < b.r;
    }
} a[NR];

long long f[NR];

struct Segment_Tree
{
    long long t[NR * 4];
    long long tag[NR * 4];

    int lc(int x) { return x << 1; }
    int rc(int x) { return x << 1 | 1; }

    void push_up(int x)
    {
        t[x] = (t[lc(x)] + t[rc(x)]) % mod;
    }

    void push_down(int x)
    {
        if (tag[x] != 1)
        {
            t[lc(x)] = (t[lc(x)] * tag[x]) % mod;
            tag[lc(x)] = (tag[lc(x)] * tag[x]) % mod;
            t[rc(x)] = (t[rc(x)] * tag[x]) % mod;
            tag[rc(x)] = (tag[rc(x)] * tag[x]) % mod;
            tag[x] = 1;
        }
    }

    void build(int x, int l, int r)
    {
        tag[x] = 1;
        if (l == r)
        {
            t[x] = f[l];
            return;
        }
        int mid = (l + r) >> 1;
        build(lc(x), l, mid);
        build(rc(x), mid + 1, r);
        push_up(x);
    }

    void multiply(int x, int l, int r, int ql, int qr, long long val)
    {
        if (ql > qr) return;
        if (ql <= l && r <= qr)
        {
            t[x] = (t[x] * val) % mod;
            tag[x] = (tag[x] * val) % mod;
            return;
        }
        int mid = (l + r) >> 1;
        push_down(x);
        if (ql <= mid) multiply(lc(x), l, mid, ql, qr, val);
        if (qr > mid) multiply(rc(x), mid + 1, r, ql, qr, val);
        push_up(x);
    }

    void modify(int x, int l, int r, int pos, long long val)
    {
        if (l == r)
        {
            t[x] = (t[x] + val) % mod;
            tag[x] = 1;
            return;
        }
        int mid = (l + r) >> 1;
        push_down(x);
        if (pos <= mid) modify(lc(x), l, mid, pos, val);
        else modify(rc(x), mid + 1, r, pos, val);
        push_up(x);
    }

    long long query(int x, int l, int r, int ql, int qr)
    {
        if (ql > qr) return 0;
        if (ql <= l && r <= qr)
        {
            return t[x];
        }
        int mid = (l + r) >> 1;
        push_down(x);
        long long res = 0;
        if (ql <= mid) res = (res + query(lc(x), l, mid, ql, qr)) % mod;
        if (qr > mid) res = (res + query(rc(x), mid + 1, r, ql, qr)) % mod;
        return res;
    }
} segt;

int main()
{
    int n, m;
    scanf("%d%d", &n, &m);
    for (int i = 1; i <= m; i ++)
    {
        scanf("%d%d", &a[i].l, &a[i].r);
    }
    sort(a + 1, a + m + 1);
    f[1] = 1;
    segt.build(1, 1, n);
    for (int i = 1; i <= m; i ++)
    {
        int l = a[i].l, r = a[i].r;
        long long sum = segt.query(1, 1, n, l, r);
        segt.modify(1, 1, n, r, sum);
        segt.multiply(1, 1, n, 1, l - 1, 2);
        segt.multiply(1, 1, n, r + 1, n, 2);
    }
    long long ans = segt.query(1, 1, n, n, n);
    printf("%lld\n", ans);
    return 0;
}
posted @ 2026-03-22 09:19  hsy8116  阅读(73)  评论(0)    收藏  举报