1 条题解

  • 0
    @ 2025-10-30 11:26:03

    「ROI 2024 Day1」2026 题解

    题目分析

    题目要求模拟一个棋盘游戏,棋盘大小为 m×nm \times n,上面有带字母的棋子。给定一个由 L,R,U,D\texttt{L,R,U,D} 组成的操作序列 ss,每次操作将所有棋子向指定方向移动,直到遇到边界或其他棋子为止。最终输出所有操作后的棋盘状态。

    数据范围

    • 所有数据组的 m×nm \times n 总和 2×106\leq 2 \times 10^6
    • 所有数据组的 qq 总和 2×106\leq 2 \times 10^6

    算法思路

    1. 操作序列优化

    由于连续的同方向操作可以合并,代码首先对操作序列进行压缩:

    • 相邻的相同方向操作只保留最后一次
    • 对于水平/垂直方向的交替,如果出现循环模式可以进一步优化

    2. 小规模情况直接模拟

    当剩余操作数 Q8Q \leq 8 时,直接调用相应的移动函数进行模拟。

    3. 大规模情况使用置换环

    对于更长的操作序列,发现操作具有周期性。通过预处理前几个操作建立位置映射关系,然后利用置换环理论批量处理剩余操作。

    代码实现详解

    坐标转换函数

    int X(int u) { return (u - 1) / m + 1; }      // 一维转二维行坐标
    int Y(int u) { return u % m ? u % m : m; }    // 一维转二维列坐标  
    int ID(int x, int y) { return (x - 1) * m + y; } // 二维转一维坐标
    

    四个方向的移动函数

    每个函数都将该方向上的所有棋子向边界紧凑排列:

    void Up(vector<int> *a) {
        FOR(j, 1, m) {
            int tmp(0);
            FOR(i, 1, n) if (~a[i][j]) {          // ~(-1) = 0,判断非空
                int num(a[i][j]);
                a[i][j] = -1, a[++tmp][j] = num;  // 向上紧凑排列
            }
        }
    }
    // Down、Left、Right 函数逻辑类似,只是遍历方向不同
    

    主处理函数 Cmain()

    1. 输入处理

    I(n, m), t = n * m;
    FOR(i, 1, n) {
        scanf("%s", s + 1);
        a[i] = vector<int>(m + 1, -1);           // -1 表示空格
        FOR(j, 1, m) if (s[j] != '.')
            a[i][j] = s[j] - 'a';                // 字母转数字 0-25
    }
    

    2. 操作序列压缩

    auto typ = [&](char a) { return a == 'L' || a == 'R'; }; // 判断水平方向
    FOR(i, 1, tmp) {
        char c(s[i]);
        while (Q && typ(s[Q]) == typ(c)) --Q;    // 合并连续同方向操作
        if (Q < 2 || s[Q - 1] != c) s[++Q] = c;  // 避免 LRLR 重复模式
    }
    

    3. 小规模直接模拟

    if (Q <= 8) {
        FOR(i, 1, Q) s[i] == 'L' ? Left(a) : ... // 根据方向调用相应函数
        return Output(a);
    }
    

    4. 大规模情况预处理

    tmp = 4 + Q % 4;        // 先处理前几个操作
    FOR(i, 1, tmp) ...      // 执行前 tmp 个操作
    Q -= tmp;
    FOR(i, 1, Q) s[i] = s[i + tmp]; // 调整剩余操作序列
    
    // 建立位置索引
    FOR(i, 1, n) FOR(j, 1, m) if (~a[i][j])
        idx[i][j] = ID(i, j);
    
    // 执行 4 个操作建立映射关系
    FOR(i, 1, 4) ... 
    
    // 构建父节点关系(置换映射)
    FOR(i, 1, n) FOR(j, 1, m) if (~idx[i][j])
        fa[idx[i][j]] = ID(i, j);
    

    5. 置换环处理

    FOR(i, 1, n) FOR(j, 1, m) if (~a[i][j] && !vis[ID(i, j)]) {
        const int u(ID(i, j));
        vector<int> tmp {u};
        
        // 寻找整个置换环
        for (int v(fa[u]); v && v != u; v = fa[v])
            tmp.push_back(v), vis[v] = true;
        
        // 计算最终位置:Q % 环长 的偏移
        int y(Q % tmp.size());
        for (int x : tmp)
            b[X(tmp[y])][Y(tmp[y])] = a[X(x)][Y(x)], 
            y = (y + 1) % tmp.size();
    }
    

    复杂度分析

    • 时间复杂度O(mn+q)O(mn + q),其中 qq 是操作序列长度
    • 空间复杂度O(mn)O(mn)

    算法亮点

    1. 操作序列压缩:减少不必要的重复操作
    2. 置换环技术:将多次重复操作转化为环上的偏移计算
    3. 坐标映射:使用一维索引简化位置关系处理
    4. 分治策略:小规模直接模拟,大规模使用数学优化

    这种方法充分利用了操作序列的周期性和置换性质,将看似复杂的多次移动转化为高效的数学计算,成功解决了大数据量下的性能问题。

    • 1

    信息

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