AtCoder Beginner Contest 434 ABCDE 题目解析

A - Balloon Trip

题意

每个气球可以撑起 \(B\) 克的重量。如果往一个物体上挂 \(n\) 个气球,只有当物体重量严格小于 \(nB\) 克时,这个物体才会升上天空。

高桥的体重是 \(W\) 千克。问至少需要在高桥身上挂多少个气球,才能让高桥升上天空?

思路

问一个最小的正整数 \(n\) 满足:

\[\begin{aligned} nB &\gt 1000 \times W \\ n &\gt \frac {1000 \times W} {B} = \lfloor \frac {1000 \times W} {B} \rfloor + 1 \end{aligned} \]

代码

void solve()
{
    int w, b;
    cin >> w >> b;
    cout << 1000 * w / b + 1; 
}

B - Bird Watching

题意

天空中有 \(N\) 只鸟在飞,分别编号为 \(1, 2, \dots, N\)

鸟的种类共有 \(M\) 种,分别编号为 \(1, 2, \dots, M\)

\(i\) 只鸟的种类为 \(A_i\),大小为 \(B_i\)

对于 \(k = 1, 2, \dots, M\),请求出第 \(k\) 种鸟的平均大小。

思路

借助计数数组统计每一种鸟的大小总和以及数量,最后计算输出即可。

代码

int sum[105], cnt[105];

void solve()
{
    int n, m;
    cin >> n >> m;
    for(int i = 1; i <= n; i++)
    {
        int a, b;
        cin >> a >> b;
        sum[a] += b;
        cnt[a]++;
    }
    for(int i = 1; i <= m; i++)
        printf("%.20f\n", 1.0 * sum[i] / cnt[i]);
}

C - Flapping Takahashi

题意

高桥决定通过气球飞上天空。

在第 \(0\) 秒,高桥的高度为 \(H\),并且他打算从现在开始飞行 \(10^9\) 秒。

高桥每秒最多可以改变 \(1\) 个单位的飞行高度。但过程中飞行高度必须保持大于 \(0\)

对于飞行高度,高桥有 \(N\) 个目标,第 \(i\) 个目标是指他的高度在第 \(t_i\) 秒时必须在区间 \([l_i, u_i]\) 范围内。

高桥是否能够实现所有的目标?

思路

在过程中我们可以维护一个区间 \([a, b]\),表示在每个目标相关的时刻,我们能够移动到的高度范围。

一开始 \(t = 0\) 时,区间为 \([H, H]\)

接下来每输入一个目标信息 \(t_i, l_i, u_i\),先处理当前的时间 \(t_i\) 与上一个目标的时间 \(t_{i-1}\) 之间的差值(没有上一个目标则为 \(0\)),两者时间差 \(t_i - t_{i-1}\) 可以用于扩大我们可到达的高度范围。

假设时间为 \(t_{i-1}\) 时可到达的高度区间为 \([a, b]\),则当时间为 \(t_i\) 时,高度最小值可以下降 \(t_i - t_{i-1}\) 个单位,但由于高度不能降至 \(0\) 及以下,则区间左端点可以修正为 \(\max(1, a - (t_i - t_{i-1}))\),同理右端点可以修正为 \(b + (t_i - t_{i-1})\)。这便是在不考虑第 \(i\) 个目标的前提下,我们在时间 \(t_i\) 所能到达的高度范围。

考虑第 \(i\) 个目标,由于该目标限制我们在时间 \(t_i\) 的高度必须在 \([l_i, u_i]\) 范围内,因此只需要两区间取交集即可,即两个左端点取较大值,两个右端点取较小值。

最后,如果某个目标无法实现,则区间内不包含任何合法数值,此时 \(a \gt b\) 成立。

单组数据时间复杂度 \(O(N)\)

代码

void solve()
{
    int n, h;
    cin >> n >> h;
    
    long long a = h, b = h, last = 0;
    // 在 last 时间能够到达的高度区间 [a, b]
    bool flag = true;
    // 标记过程中是否有某个目标无法实现
    
    for(int i = 1; i <= n; i++)
    {
        long long t, l, u;
        cin >> t >> l >> u;
        
        a = max(1LL, a - (t - last));
        b = b + (t - last);
        last = t;
        
        a = max(a, l);
        b = min(b, u);
        
        if(a > b)
            flag = false;
    }
    
    if(flag == false)
        cout << "No\n";
    else
        cout << "Yes\n";
}
signed main()
{
    int T;
    cin >> T;
    while(T--)
    {
        solve();
    }
    return 0;
}

D - Clouds

题意

天空由一个 \(2000 \times 2000\) 的网格表示。当仰望天空时,我们将第 \(r\) 行第 \(c\) 列的单元格称作 \((r,c)\)

目前,天空中漂浮着 \(N\) 朵云,分别编号为 \(1,2,\dots,N\)

当且仅当单元格 \((r,c)\) 满足 \(U_i \le r \le D_i\)\(L_i \le c \le R_i\) 时,它才会被第 \(i\) 朵云所覆盖。

对于每个 \(k=1,2,\dots,N\),回答以下问题:

  • 如果从这 \(N\) 朵云中删除第 \(k\) 朵云,问此时有多少个单元格没有被任何一朵云覆盖?

思路

先借助二维差分数组将每朵云表示的子矩阵内每个单元格全部 \(+1\),再通过二维前缀和维护每个单元格分别被多少朵云覆盖。

如果某个单元格没有被任何云覆盖,则该位置恒为答案,用一个变量 sum 记录恒为答案的位置数量。

而如果某个单元格只被一朵云覆盖,那之后只需要在处理这朵云的询问时,将当前单元格加入到这朵云的答案里即可。但现在我们并不知道覆盖当前单元格的这朵云编号是什么,但这无关紧要,我们只需要再来一个二维数组 b[][],用于维护有哪些单元格只被一朵云覆盖。如果 \((i, j)\) 只被一朵云覆盖,则 b[i][j] 记作 \(1\),否则记作 \(0\)

等到所有位置全部统计完成后,对 b[][] 数组再做一次二维前缀和,以便于我们接下来针对每朵云所覆盖的子矩阵,快速计算子矩阵内部有多少个单元格只被一朵云覆盖,最后加上 sum 后输出即可。

时间复杂度 \(O(N + M^2)\)\(M = 2000\)

代码

int n;
int u[200005], d[200005], l[200005], r[200005];
int a[2005][2005], b[2005][2005];
// a[i][j] 表示 (i, j) 被多少朵云覆盖
// b[i][j] 初始表示 (i, j) 是否只被一朵云覆盖

void solve()
{
    cin >> n;
    for(int i = 1; i <= n; i++)
    {
        cin >> u[i] >> d[i] >> l[i] >> r[i];
        // 子矩阵全部 +1
        a[u[i]][l[i]]++;
        a[u[i]][r[i] + 1]--;
        a[d[i] + 1][l[i]]--;
        a[d[i] + 1][r[i] + 1]++;
    }
    
    int sum = 0; // 有多少位置一朵云也没有
    
    for(int i = 1; i <= 2000; i++)
        for(int j = 1; j <= 2000; j++)
        {
            a[i][j] = a[i][j]
                    + a[i - 1][j]
                    + a[i][j - 1]
                    - a[i - 1][j - 1];
            if(a[i][j] == 1)
                b[i][j]++; // (i, j) 只被一朵云覆盖,标记该位置
            else if(a[i][j] == 0)
                sum++;
        }
    
    for(int i = 1; i <= 2000; i++)
        for(int j = 1; j <= 2000; j++)
            b[i][j] = b[i][j]
                    + b[i - 1][j]
                    + b[i][j - 1]
                    - b[i - 1][j - 1];
    
    for(int i = 1; i <= n; i++)
    {
        int cnt = b[d[i]][r[i]]
                - b[d[i]][l[i] - 1]
                - b[u[i] - 1][r[i]]
                + b[u[i] - 1][l[i] - 1]; // 子矩阵内部只被一朵云覆盖的网格数量
        cout << sum + cnt << "\n";
    }
}

E - Distribute Bunnies

题意

在一条数轴上有 \(N\) 只兔子,编号为 \(1\)\(N\) 。第 \(i\) 只兔子位于坐标 \(X_i\),其跳跃力为 \(R_i\)。同一坐标上可能有多只兔子。

现在所有兔子都会跳跃一次。对于一只位于坐标 \(x\) 且跳跃能力为 \(r\) 的兔子而言,跳跃一次后它会移动到坐标 \(x+r\) 或坐标 \(x-r\) 处。

如果我们可以自由地选择让每只兔子跳到哪个坐标,请找出在所有兔子跳完后,最多能有多少个不同的坐标存在兔子

思路一

对于一只位于坐标 \(x\) 且跳跃能力为 \(r\) 的兔子,可以在 \(x - r\)\(x + r\) 两个数之间选择一个数字当作它的最终位置。

可以尝试将 \(x - r\)\(x + r\) 当作图中的两点,并在这两点间进行连边,那么问题便变成了“对于图中的每条边,都有一次机会可以选择其两端点中的任意一点进行染色,问最多可以对多少个点进行染色”。

考虑图中的每一个连通块对答案的贡献。因为图中环的点数与边数相同,进一步思考发现只要连通块中存在至少一个环,我们就有办法为环上的每条边都分配一个点进行染色,所有点便都可以被染上色,然后再从这个环出发,每多一条连向这个环的边,就将下一个点分配给这条边,因此这种情况下连通块上的所有点均能够被染色。

反之,另一种极端情况的连通块就是树了,因为树上不存在任何环。对于一棵包含 \(n\) 个点的树而言,其只有 \(n-1\) 条边,因此最多只能够对 \(n-1\) 个点进行染色,剩余的一个点无法被染色。

也就是说,这张图中有多少棵树,就会有多少个点无法被染色,换到原题意内,就说明有多少个可能出现的位置是无法被兔子占据的。

所以只需要先借助 set 容器,对于每只兔子把所有可能安排的位置(\(x-r\)\(x+r\))全部存起来,去重后得出总共有多少个可以安排兔子的点。然后按照上述步骤建图后,判断有多少个连通块是一棵树,就减去等数量的点后输出即可。

判断树的方法有多种,可以深搜判环,或者判断边数与点数关系等。至于存图,借助 map 嵌套 vector 建立邻接表即可,或者对所有可能出现的坐标离散化后再建立邻接表。

以下代码采用 map 嵌套 vector 建立邻接表,以深搜判环的方法实现,时间复杂度 \(O(N\log^2 N)\)

代码一

int n;
set<int> st, vis;
// st 存储所有可能的位置
// vis 标记每个位置是否已被访问过
map<int, vector<int>> G;
// 邻接表 G[x] 存储与位置 x 相连的边的信息

bool dfs(int u, int fa)
{
    vis.insert(u);
    for(int &v : G[u])
    {
        if(v == fa)
            continue;
        if(vis.count(v) == 1)
            return true; // 有环
        if(dfs(v, u))
            return true;
    }
    return false;
}

void solve()
{
    cin >> n;
    for(int i = 1; i <= n; i++)
    {
        int x, r;
        cin >> x >> r;
        st.insert(x - r);
        st.insert(x + r);
        G[x - r].push_back(x + r);
        G[x + r].push_back(x - r);
    }
    int ans = st.size();
    for(int i : st)
    {
        if(vis.count(i) == 0)
        {
            if(!dfs(i, 2147483647))
                ans--; // 如果 i 点所在连通块是一棵树,可匹配位置 -1
        }
    }
    cout << ans;
}

思路二

可通过“兔子 - 可行位置”建立二分图,每只兔子只会对应两个可行位置,因此边数与点数量级相同。

然后直接跑最大流的 Dinic 模板即可,因为是二分图且图为单位网络,故时间复杂度 \(O(N\sqrt N)\)

posted @ 2025-11-29 21:54  StelaYuri  阅读(5)  评论(0)    收藏  举报