AtCoder Beginner Contest 061

A

给三个整数 \(A, B, C\) ,确定是否有 \(C \geq A, C \leq B\) 。即 \(A \leq C \leq B\)

    int A, B, C; std::cin >> A >> B >> C;
    std::cout << ((A <= C && C <= B) ? "Yes" : "No") << "\n";

B

题意:
\(N\) 个城市 \(M\) 条路,第 \(i(1 \leq i \leq M)\) 条路双向地连接城市 \(a_i\)\(b_i\)\(1 \leq a_i, b_i \leq N\))。
询问每个城市有多少条公路。

\(1 \leq N, M \leq 50, 1\leq a_i, b_i \leq N, a_i \neq b_i\)

题解:
即询问每个点的度。

    int N, M; std::cin >> N >> M;
    std::vector<int> d(N + 1);
    for (int i = 1; i <= M; i++) {
        int u, v; std::cin >> u >> v;
        d[u]++; d[v]++;
    }
    for (int i = 1; i <= N; i++) std::cout << d[i] << "\n";

时间复杂度 \(O(N + M)\)

C

题意:
有一个空集合。有 \(N\) 次操作将会插入元素到空数组中。第 \(i\) 次操作,插入 \(b_i\)\(a_i\) 到原始集合中。
询问插入操作结束后,第 \(K\) 小的数。

\(1 \leq N, a_i, b_i \leq 10^{5}, 1 \leq K \leq \sum_{i = 1}^{N} b_i\)

题解:

首先不需要离散化(没有很大意义)。

考虑直接维护一个权值数组,求出前缀和。类似一维数点,二分第 \(K\) 小。

const int MAXN = 1E5+10;
ll a[MAXN];
void solve() {
    int N; ll K;
    std::cin >> N >> K;
    int mx = 0;
    for (int i = 1; i <= N; i++) {
        int x, y; std::cin >> x >> y;
        a[x] += y;
        mx = std::max(mx, x);
    }
    for (int i = 1; i <= mx; i++) a[i] += a[i - 1];
    int L = 0, R = mx + 1;
    while ( R - L > 1 ) {
        int M = L + R >> 1;
        if (a[M] >= K) R = M;
        else L = M;
    }
    std::cout << R << "\n";
}

时间复杂度 \(O(N + \log N)\)

给题的方式很奇怪,原 idea 似乎是个动态询问。

实际上就是动态全局第 K 小问题。在 BIT 上倍增查找做到 \(O(N \log N)\)

再难一点会套一个离散化的实现。

D

题意:
一个图,有 \(N\) 个点 \(M\) 条边,第 \(i\) 条边从 \(a_i\) 连向 \(b_i\) ,权值为 \(c_i\)

棋子一开始在 \(1\) 号点,玩家可以让棋子沿着有向边走,且可以在 \(N\) 号点选择结束。保证点 \(1\) 到点 \(N\) 存在通路。

一个棋子可以从 \(u\) 走到 \(v\) ,当且仅当 \(u\)\(v\) 连接了一条边。并且可以获得 \(c_{e(u, v)}\) 的积分。

询问游戏结束时玩家可以得到的最大积分。若可以得到无限积分则输出 \(inf\)

\(2 \leq N \leq 100, 1 \leq M \leq min(\frac{N(N - 1)}{2}, 2000)\)
\(1 \leq a_i, b_i \leq N(1 \leq i \leq M), a_i \neq b_i, a_i \neq a_j, b_i \neq b_j\) 即没有自环和重边
\(-10^{9} \leq c_i \leq 10^{9}\)

题解:

若存在正环,则 \(s\) 一定在该正环内。只要保证这个正环于 \(s\)\(t\) 的路径和某个正环有交 。则可以得到无限积分。
只需要让包含 \(s\) 的负环可达 \(t\) 即可。

否则找 \(1\)\(N\) 的最长路。

1. 找正环

首先环是好找的。最小环也是好找的。

如何找负环?使用 Bellman-ford 算法(SPFA 是卡常,且一定卡不过)。
如何找正环?在反权图上找负环。

Bellman-ford 实际上是反复迭代的暴力。每条边最多松弛 \(N - 1\) 次,于是 Beelman-ford 最坏有 \(N - 1\) 轮迭代。可以通过 \(O(N M)\) 的时间复杂度求出非正权图的单源最短路。

找负环:
对于一个点 \(s\) ,若从 \(s\) 开始的 Bellman-ford 迭代轮数 \(\geq N\) ,则图上存在负环 \(G\) 满足 \(s \in G\)

并且可以再跑一轮 Bellman-ford 找到 \(G\)

\(e\) 会且能被松弛,一定满足 \(e_u, e_v \in G\)

容易证明每一轮迭代,负环上的边一定会松弛。 负环被确定后。每个点到源点的距离一定小于上一轮的距离。否则这个点不在负环上。

有向图上,环可达的点:

在得到负环的情况下,还要找负环可达的点。

若一条边 \(e\) 满足 \(e_u \in G\) 或者 \(e_u\) 被负环可达,则 \(e_v\) 被负环可达。

每次迭代最坏扩展一个可达节点,于是最坏需要需要 \(N - 1\) 轮迭代。

找正环的时间复杂度为 \(O(N M)\)

2. 单源最长路

如果不是 DAG 。反权图上的单源最短路即原图上的单元最短路。显然是 Bellman-ford时间复杂度 \(O(N M)\)

    int N, M; std::cin >> N >> M;
    std::vector<std::array<int, 3> > E(M + 1);
    for (int i = 1; i <= M; i++) {
        int u, v, c; std::cin >> u >> v >> c;
        E[i] = {u, v, c};
    }
    std::vector<std::array<int, 3> > anti_E(E.begin(), E.end());
    for (int i = 1; i <= M; i++) anti_E[i][2] *= -1;
    
    std::vector<ll> dist(N + 1, llinf);
    dist[1] = 0;
    for (int i = 1; i <= N; i++) {
        for (int j = 1; j <= M; j++) {
            int u = anti_E[j][0], v = anti_E[j][1], c = anti_E[j][2];
            if (dist[u] == llinf) continue;
            if (dist[u] + c < dist[v]) {
                dist[v] = dist[u] + c;
            }
        }
    }
    
    std::vector<int> negative(N + 1);
    std::vector<int> can(N + 1);
    for (int j = 1; j <= M; j++) {
        int u = anti_E[j][0], v = anti_E[j][1], c = anti_E[j][2];
        if (dist[u] == llinf) continue;
        if (dist[u] + c < dist[v])
            negative[v] = can[v] = 1;
    }
    for (int i = 1; i <= N - 1; i++) {
        for (int j = 1; j <= M; j++) {
            int u = anti_E[j][0], v = anti_E[j][1], c = anti_E[j][2];
            if (dist[u] == llinf) continue;
            if (can[u])
                can[v] = 1;
        }
    }
    
    if (can[N]) std::cout << "inf\n";
    else std::cout << -dist[N] << "\n";
posted @ 2024-05-08 15:05  03Goose  阅读(59)  评论(0)    收藏  举报
📑 文章目录