1 条题解

  • 0
    @ 2026-5-16 14:47:06

    B. Pushing Balls 详细题解

    一、问题重述

    有一个 n×mn \times m 的网格,初始为空。每次操作可以:

    • 从某行的最左边缘推入一个球,该球会向右移动,遇到球则让原球继续右移,自己停下
    • 从某列的最上边缘推入一个球,该球会向下移动,遇到球则让原球继续下移,自己停下

    给定最终网格(11 表示有球,00 表示无球),判断是否能通过一系列操作达到该状态。


    二、核心观察

    2.1 操作的本质

    • 从第 ii 行左边缘推球:相当于将第 ii 行最左边的 00 变成 11,且该行该列右侧的球依次右移(但最终状态固定,我们只关心能否实现)
    • 从第 jj 列上边缘推球:相当于将第 jj 列最上边的 00 变成 11,且该列下方的球依次下移

    关键:每次操作只能把某个行/列最前端的 00 变成 11,不能凭空在中间产生 11

    2.2 逆向思考

    从最终状态出发,逆向操作:

    • 如果一个 11上方全是 11 或者左方全是 11,那么它可能是最后一次推球产生的(因为从左边或上边推球时,它挡住了后面的球)
    • 逆向操作:将这样的 11 变回 00,不会破坏可推性

    重复此过程,如果能将所有 11 消去,则原状态可达。

    2.3 必要条件

    对于任意一个 11,如果它的上方存在 00左方也存在 00,那么这个 11 永远无法通过任何正向操作产生:

    • 从左边推球:只能把最左边 0011,无法跳过左边的 00 去产生它
    • 从上边推球:只能把最上边 0011,无法跳过上边的 00 去产生它
    • 从其他行/列推球:不会影响这个位置

    因此,必要条件是:每个 11 要么上方全是 11,要么左方全是 11


    三、充分性证明

    如果每个 11 都满足上方无 00 或左方无 00,则我们可以按 i+ji+j 从大到小(即从右下角向左上角)的顺序,依次将 11 逆向消去:

    • 取当前 i+ji+j 最大的 11,它一定是某行最右边的 11 或某列最下边的 11
    • 由于它满足条件,它的上方全是 11 或左方全是 11,因此它可以通过一次逆向操作(从左边或上边推入的逆过程)消去
    • 消去后,剩余网格仍然满足该性质

    因此可以一直消到全 00,原状态可达。


    四、标程代码(带详细注释)

    #include<bits/stdc++.h>
    using namespace std;
    typedef long long ll;
    
    ll t, n, m, a[59][59], vis[59][59];
    char s[59];
    
    inline ll read() {
        ll s = 0, w = 1;
        char ch = getchar();
        while (ch > '9' || ch < '0') {
            if (ch == '-') w = -1;
            ch = getchar();
        }
        while (ch <= '9' && ch >= '0') {
            s = (s << 1) + (s << 3) + (ch ^ 48);
            ch = getchar();
        }
        return s * w;
    }
    
    int main() {
        t = read();
        while (t--) {
            n = read(), m = read();
            
            // 读入网格
            for (ll i = 1; i <= n; i++) {
                scanf("%s", s + 1);
                for (ll j = 1; j <= m; j++) {
                    a[i][j] = s[j] - '0';
                    vis[i][j] = 0;
                }
            }
            
            // 标记所有能从左边连续推到的位置
            // 即每行从第 1 列开始,连续的一段 1 都能被左边推球产生
            for (ll i = 1; i <= n; i++) {
                for (ll j = 1; j <= m; j++) {
                    if (!a[i][j]) break;      // 遇到 0 就停止
                    vis[i][j] = 1;            // 标记这个 1 能被左边推球产生
                }
            }
            
            // 标记所有能从上边连续推到的位置
            // 即每列从第 1 行开始,连续的一段 1 都能被上边推球产生
            for (ll j = 1; j <= m; j++) {
                for (ll i = 1; i <= n; i++) {
                    if (!a[i][j]) break;      // 遇到 0 就停止
                    vis[i][j] = 1;            // 标记这个 1 能被上边推球产生
                }
            }
            
            // 检查是否所有 1 都被标记
            bool fl = 1;
            for (ll i = 1; i <= n && fl; i++) {
                for (ll j = 1; j <= m; j++) {
                    if (a[i][j] && !vis[i][j]) {
                        fl = 0;               // 存在一个 1 既不能从左边也不能从上边连续到达
                        break;
                    }
                }
            }
            
            puts(fl ? "YES" : "NO");
        }
        return 0;
    }
    

    五、算法逻辑总结

    步骤 操作
    1 读入网格,记录每个位置是否有球
    2 逐行扫描:每行从第 1 列开始,标记连续的一段 11(这些 11 可以通过从该行左边推球产生)
    3 逐列扫描:每列从第 1 行开始,标记连续的一段 11(这些 11 可以通过从该列上边推球产生)
    4 检查所有 11 是否都被标记。若有未标记的 11,输出 "NO";否则输出 "YES"

    六、示例验证

    示例 1

    001
    001
    110
    
    • 左边标记:第 1 行:0 开头 → 无标记;第 2 行同理;第 3 行:1,1,0 → 标记 (3,1),(3,2)
    • 上边标记:第 1 列:0,0,1 → 标记 (3,1);第 2 列:0,0,1 → 标记 (3,2);第 3 列:1,1,0 → 标记 (1,3),(2,3)
    • 所有 11 都被标记 → YES

    示例 5(最后一个 NO)

    000
    000
    001
    
    • 左边标记:第 3 行:0,0,1 → 第 1 列是 00,break → 无法标记 (3,3)
    • 上边标记:第 3 列:0,0,1 → 第 1 行是 00,break → 无法标记 (3,3)
    • (3,3) 未被标记 → NO

    七、复杂度分析

    • 每个测试用例:O(nm)O(n \cdot m)
    • nm10000n \cdot m \le 10000t10000t \le 10000,完全可行

    八、总结

    本题的核心转化:

    • 正向:每次操作只能把某行/列最前端的 00 变成 11
    • 逆向:每次只能消去一个上方全 11 或左方全 1111
    • 结论:所有 11 必须满足“上方无 00”或“左方无 00
    • 1

    信息

    ID
    7102
    时间
    1000ms
    内存
    256MiB
    难度
    10
    标签
    递交数
    1
    已通过
    1
    上传者