#P3630. 第3题-消消乐
          
                        
                                    
                      
        
              - 
          
          
                      1000ms
            
          
                      Tried: 13
            Accepted: 9
            Difficulty: 7
            
          
          
          
                       所属公司 : 
                              阿里
                                
            
                        
              时间 :2025年9月9日-菜鸟
                              
                      
          
 
- 
                        算法标签>模拟          
 
第3题-消消乐
思路
这个问题的核心是模拟整个游戏过程,直到达到稳定状态。我们可以使用一个主循环来不断处理每一轮的消除和下落,直到某一轮没有任何消除发生时,模拟结束。
每一轮模拟包含以下三个关键步骤:
- 
标记阶段
- 我们需要首先找出所有可被消除的数字。为了处理“同时消除”的规则,我们不能在找到一个组后立即消除它,因为这会影响后续的检测。
 - 一个好的策略是创建一个与游戏网格大小相同的布尔型二维数组(例如 
to_eliminate[H][5]),并将其所有值初始化为false。 - 我们遍历整个网格。对于每个单元格 
(r, c),我们向右检查是否存在连续3个或更多相同的数字。例如,检查grid[r][c] == grid[r][c+1] == grid[r][c+2]。 - 如果找到了一个这样的组,就将这个组中所有数字对应的 
to_eliminate数组中的位置标记为true。这个过程需要覆盖所有长度大于等于3的情况(例如4连珠,5连珠)。 - 在遍历完整个网格后,
to_eliminate数组就记录了本轮所有需要消除的数字。 
 - 
消除与计分阶段
- 在此阶段,我们首先需要一个变量来判断本轮是否发生了消除。如果在标记阶段有任何一个单元格被标记为 
true,则说明本轮有消除发生,模拟需要继续。如果标记阶段结束后没有任何单元格被标记,说明游戏达到稳定状态,我们可以直接跳出主循环。 - 我们遍历 
to_eliminate数组。如果to_eliminate[r][c]为true,我们将grid[r][c]的值累加到总分total_sum中。 - 同时,我们将 
grid[r][c]的值设为0,用0代表该单元格已被清空。 
 - 在此阶段,我们首先需要一个变量来判断本轮是否发生了消除。如果在标记阶段有任何一个单元格被标记为 
 - 
下落阶段
- 消除后,网格中会出现很多 
0(空位)。我们需要让上方的数字掉落下来填补这些空位。这个操作需要对每一列独立进行。 - 对于每一列(共 
5列),一个高效的方法是使用“双指针”或类似的逻辑。我们可以为该列创建一个临时列表,只包含所有非0的数字,然后用0填充到列表的顶部,直到其大小等于行数H。最后将这个临时列表的内容复制回原网格的对应列。 - 一个更原地(in-place)的方法是:从下往上遍历该列。使用一个 
write_ptr指针指向列的底部(第$H-1$行),再用一个read_ptr从底部开始向上扫描。当read_ptr指向一个非0的数字时,就将这个数字复制到write_ptr指向的位置,并将write_ptr上移一位(write_ptr--)。扫描结束后,write_ptr上方的所有位置都应填充为0。 
 - 消除后,网格中会出现很多 
 
重复以上三个步骤,直到某一轮不再有任何消除。最终累加的 total_sum 即为答案。
C++
#include <iostream>
#include <vector>
#include <numeric>
using namespace std;
// 解决单个测试用例的函数
void solve() {
    int H;
    cin >> H;
    if (H == 0) {
        cout << 0 << endl;
        return;
    }
    vector<vector<int>> grid(H, vector<int>(5));
    for (int i = 0; i < H; ++i) {
        for (int j = 0; j < 5; ++j) {
            cin >> grid[i][j];
        }
    }
    long long total_score = 0;
    while (true) {
        bool eliminated_in_round = false;
        vector<vector<bool>> to_eliminate(H, vector<bool>(5, false));
        // 1. 标记阶段:找出所有可消除的方块
        for (int i = 0; i < H; ++i) {
            for (int j = 0; j <= 5 - 3; ++j) {
                // 找到连续三个或更多的起始点
                if (grid[i][j] != 0 && grid[i][j] == grid[i][j+1] && grid[i][j] == grid[i][j+2]) {
                    int current_val = grid[i][j];
                    int k = j;
                    // 向右延伸,标记所有相同的连续方块
                    while (k < 5 && grid[i][k] == current_val) {
                        to_eliminate[i][k] = true;
                        k++;
                    }
                }
            }
        }
        // 2. 消除与计分阶段
        long long round_score = 0;
        for (int i = 0; i < H; ++i) {
            for (int j = 0; j < 5; ++j) {
                if (to_eliminate[i][j]) {
                    eliminated_in_round = true; // 标记本轮发生了消除
                    round_score += grid[i][j];
                    grid[i][j] = 0; // 将方块设为0,表示空
                }
            }
        }
        
        total_score += round_score;
        // 如果本轮没有消除任何方块,则模拟结束
        if (!eliminated_in_round) {
            break;
        }
        // 3. 下落阶段
        for (int j = 0; j < 5; ++j) { // 对每一列进行操作
            int write_row = H - 1; // 写入指针,从该列底部开始
            for (int read_row = H - 1; read_row >= 0; --read_row) { // 读取指针,从下往上扫描
                if (grid[read_row][j] != 0) {
                    // 将非空方块移动到写入位置
                    grid[write_row][j] = grid[read_row][j];
                    write_row--;
                }
            }
            // 将写入指针上方的所有行填充为0
            while (write_row >= 0) {
                grid[write_row][j] = 0;
                write_row--;
            }
        }
    }
    cout << total_score << endl;
}
int main() {
    // 提高C++ IO效率
    ios_base::sync_with_stdio(false);
    cin.tie(NULL);
    int T;
    cin >> T;
    while (T--) {
        solve();
    }
    return 0;
}
Python
def solve():
    """
    解决单个测试用例
    """
    try:
        H_str = input()
        if not H_str:  # 处理可能的空行输入
            return
        H = int(H_str)
        grid = []
        for _ in range(H):
            grid.append(list(map(int, input().split())))
    except (IOError, ValueError):
        return
    if H == 0:
        print(0)
        return
    total_score = 0
    while True:
        to_eliminate = [[False for _ in range(5)] for _ in range(H)]
        eliminated_in_round = False
        # 1. 标记阶段
        for r in range(H):
            for c in range(5 - 2):
                if grid[r][c] != 0 and grid[r][c] == grid[r][c+1] == grid[r][c+2]:
                    val = grid[r][c]
                    # 向右延伸标记
                    k = c
                    while k < 5 and grid[r][k] == val:
                        to_eliminate[r][k] = True
                        k += 1
        # 2. 消除与计分
        round_score = 0
        for r in range(H):
            for c in range(5):
                if to_eliminate[r][c]:
                    eliminated_in_round = True
                    round_score += grid[r][c]
                    grid[r][c] = 0
        
        total_score += round_score
        
        # 如果没有消除发生,结束循环
        if not eliminated_in_round:
            break
        # 3. 下落阶段
        for c in range(5):
            # 提取该列所有非0的数字
            non_zeros = [grid[r][c] for r in range(H) if grid[r][c] != 0]
            # 计算需要补充的0的数量
            num_zeros = H - len(non_zeros)
            # 构建新列:顶部是0,底部是原来的数字
            new_col = [0] * num_zeros + non_zeros
            # 将新列写回网格
            for r in range(H):
                grid[r][c] = new_col[r]
    print(total_score)
def main():
    try:
        T_str = input()
        T = int(T_str) if T_str else 0
        for _ in range(T):
            solve()
    except (IOError, ValueError):
        return
if __name__ == "__main__":
    main()
Java
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.IOException;
import java.util.StringTokenizer;
public class Main {
    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        int T = Integer.parseInt(br.readLine().trim());
        while (T-- > 0) {
            solve(br);
        }
    }
    private static void solve(BufferedReader br) throws IOException {
        String hLine = br.readLine();
        if (hLine == null || hLine.trim().isEmpty()) {
            return;
        }
        int H = Integer.parseInt(hLine.trim());
        if (H == 0) {
            System.out.println(0);
            return;
        }
        int[][] grid = new int[H][5];
        for (int i = 0; i < H; i++) {
            StringTokenizer st = new StringTokenizer(br.readLine());
            for (int j = 0; j < 5; j++) {
                grid[i][j] = Integer.parseInt(st.nextToken());
            }
        }
        long totalScore = 0;
        while (true) {
            boolean eliminatedInRound = false;
            boolean[][] toEliminate = new boolean[H][5]; // 默认为 false
            // 1. 标记阶段
            for (int i = 0; i < H; i++) {
                for (int j = 0; j <= 5 - 3; j++) {
                    if (grid[i][j] != 0 && grid[i][j] == grid[i][j+1] && grid[i][j] == grid[i][j+2]) {
                        int val = grid[i][j];
                        int k = j;
                        // 向右延伸标记
                        while (k < 5 && grid[i][k] == val) {
                            toEliminate[i][k] = true;
                            k++;
                        }
                    }
                }
            }
            // 2. 消除与计分
            long roundScore = 0;
            for (int i = 0; i < H; i++) {
                for (int j = 0; j < 5; j++) {
                    if (toEliminate[i][j]) {
                        eliminatedInRound = true;
                        roundScore += grid[i][j];
                        grid[i][j] = 0; // 设为0代表空
                    }
                }
            }
            
            totalScore += roundScore;
            // 如果本轮没有消除,则结束
            if (!eliminatedInRound) {
                break;
            }
            // 3. 下落阶段
            for (int j = 0; j < 5; j++) { // 对每一列操作
                int writeRow = H - 1; // 写入指针,从底部开始
                for (int readRow = H - 1; readRow >= 0; readRow--) { // 读取指针,从下往上
                    if (grid[readRow][j] != 0) {
                        grid[writeRow][j] = grid[readRow][j];
                        writeRow--;
                    }
                }
                // 将写入指针上方的所有位置填充为0
                while (writeRow >= 0) {
                    grid[writeRow][j] = 0;
                    writeRow--;
                }
            }
        }
        System.out.println(totalScore);
    }
}
        题目内容
图图在玩一个消消乐的游戏,在这个游戏中使用了一个 H 行 ×5 列单元格的网格,如下图所示。一块刻有数字 (1 ~ 9) 的石头被放置在每个单元格中。当水平相邻的格子中的三个或更多石头上刻有相同的数字时,这些石头就会消失。如果格子里有石头,石头消失了,上面格子里的石头就会掉下来,填补空缺。

游戏采取以下步骤。
1.当水平相邻的格子中的三个及以上的石头上刻有相同的数字时,这些石头就会消失。
2.当石头位于空单元上方的单元中时,这些石头会下降,以便填充空单元。
3.如果在当前状态下,有一组或多组石头满足消失条件,则会同时消失,(即消失是同步的)。
4.重复上述步骤,直到网格中的石头无法再通过上述步骤消失,

现在请你编写一个程序,求出消失的石头上数字的总和。
输入描述
第一行给出测试用例的数量 T ,
对于每一组用例第一行给出 H ,表示网格的行。
接下来 H 行 5 列,给出网格的信息,第 i 行 j 列的数字为 xi,j ,用空格分隔。
1≤T≤1100
1≤H≤10
1≤xi,j<9
输出描述
对于每一组测试用例,输出消失的石头上数字的总和。
样例1
输入
1
1
6 9 9 9 9
输出
36
说明
4 个连续的 9 消失
样例2
输入
1
5
5 9 5 5 9
5 5 6 9 9
4 6 3 6 9
3 3 2 9 9
2 2 1 1 1
输出
38
说明
见样图解释