MAZE(2019牛客暑期多校训练营(第二场)E,线段树 + 矩阵乘法)

本文介绍了一种解决迷宫中特定路径计数问题的方法,通过动态规划与矩阵运算结合,实现了高效的方案数计算,并提供了完整的C++代码实现。

一.题目链接:

MAZE

二.题目大意:

给一个 n × m 大小由{0,1}构成的矩形,Q 次询问.

0 可以走,1 不可以走.

每次走只能向下,左,右方向,且不能走重复的位置.

每次询问有三个整数:q,a,b.

当 q 为 1 时,将点(a,b)取反.

当 q 为 2 时,求出从点(1,a)走到点(n,b)的方案数 % (1e9  +7).

三.分析:

先考虑一个点的方案数求解.

这里的最后一步为向左,向右或向下.

可推得:包含点(a,b)同行的某一段区间全为 0.

不妨设这段区间为 [l, r], f[i][j]为从点(1,1)到达(i,j)的方案数.

立即推:f[i][j] = \sum^{r}_{k = l} f[i - 1][k]

所以 f[i][j] 是上一行方案数的线性组合.

现在考虑第 i 行各个元素的方案数.

同理可得:\begin{pmatrix} f[i][1] &f[i][2] &.... &f[i][m] \end{pmatrix}^{T} = A _{i}\begin{pmatrix} f[i -1][1] &f[i - 1][2] &.... &f[i - 1][m] \end{pmatrix}^{T}

整理可得:\begin{pmatrix} f[i][1] &f[i][2] &.... &f[i][m] \end{pmatrix}^{T} = A _{i} A _{i -1}... A _{2}\begin{pmatrix} f[1][1] &f[1][2] &.... &f[1][m] \end{pmatrix}^{T}

其中 f[1][1]=f[1][2]=...=f[1][m] = 1

因此,不妨设 A_{1} = \begin{bmatrix} 1 & ... & 1\\ ...& &...\\ 1& ...& 1 \end{bmatrix}A = A_{1}A_{2}...A_{n}

根据矩阵乘法可知:从点(1,a)到点(n,b)的方案数为 A_{a,b}

四.代码实现:

#include <set>
#include <map>
#include <ctime>
#include <queue>
#include <cmath>
#include <stack>
#include <bitset>
#include <vector>
#include <cstdio>
#include <sstream>
#include <cstring>
#include <cstdlib>
#include <iostream>
#include <algorithm>
#define eps 1e-6
#define lc k * 2
#define rc k * 2 + 1
#define pi acos(-1.0)
#define ll long long int
using namespace std;

const int M = (int)5e4;
const ll mod = (ll)1e9 + 7;
const ll inf = 0x3f3f3f3f3f;

int n, m, Q;
int maze[M + 5][15];

struct node
{
    ll D[15][15];
}tree[M * 4 + 5];

node mul(node a, node b)
{
    struct node c;
    memset(c.D, 0, sizeof(c.D));
    for(int i = 1; i <= m; ++i)
    {
        for(int j = 1; j <= m; ++j)
        {
            for(int k = 1; k <= m; ++k)
            {
                c.D[i][j] = (c.D[i][j] + a.D[i][k] * b.D[k][j] % mod) % mod;
            }
        }   
    }
    return c;
}

void build(int k, int l, int r)
{
    if(l == r)
    {
        for(int i = 1; i <= m; ++i)
        {
            for(int j = i; j <= m && !maze[l][j]; ++j)
                tree[k].D[i][j] = 1;
            for(int j = i; j >= 1 && !maze[l][j]; --j)
                tree[k].D[i][j] = 1;
        }
        return;
    }
    int mid = (l + r) >> 1;
    build(lc, l, mid);
    build(rc, mid + 1, r);
    tree[k] = mul(tree[lc], tree[rc]);
}

void update(int k, int l, int r, int a, int b)
{
    if(l == r && l == a)
    {
        memset(tree[k].D, 0, sizeof(tree[k].D));
        for(int i = 1; i <= m; ++i)
        {
            for(int j = i; j <= m && !maze[l][j]; ++j)
                tree[k].D[i][j] = 1;
            for(int j = i; j >= 1 && !maze[l][j]; --j)
                tree[k].D[i][j] = 1;
        }
        return;
    }
    int mid = (l + r) >> 1;
    if(a <= mid)
        update(lc, l, mid, a, b);
    else
        update(rc, mid + 1, r, a, b);
    tree[k] = mul(tree[lc], tree[rc]);
}

int main()
{
    scanf("%d %d %d", &n, &m, &Q);
    for(int i = 1; i <= n; ++i)
    {
        for(int j = 1; j <= m; ++j)
            scanf("%1d", &maze[i][j]);
    }
    build(1, 1, n);
    int q, a, b;
    while((Q--))
    {
        scanf("%d %d %d", &q, &a, &b);
        if(q == 1)
        {
            maze[a][b] = !maze[a][b];
            update(1, 1, n, a, b);
        }
        else if(q == 2)
        {
            printf("%lld\n", tree[1].D[a][b]);
        }
    }
    return 0;
}

 

<think>首先,用户查询是关于二维数组矩阵的编程题目,特别是来自LeetCode和网的。我需要基于系统级指令来构建我的回答。 系统级指令包括: - 所有行内数学表达式用 `$...$` 格式。 - 独立公式用 `$$...$$` 格式并单独成段。 - LaTeX语法正确。 - 使用中文回答。 - 生成相关问题。 - 在回答中引用的段落末尾自然地添加引用标识,如 `[^1]`。 - 回答结构清晰,帮助用户逐步解决问题,保证真实可靠。 用户引用了几个来源: - 引用[1]:关于一个序列计算的代码。 - 引用[2]:包含个算法问题,如迷宫搜索、选择排序等。 - 引用[3]:一个Java代码,用于在6x6矩阵中找到最大礼物价值。 - 引用[4]:剑指Offer的二维数组查找问题。 - 引用[5]:网的LeetCode在线编程链接。 用户说:“我想查找与二维数组矩阵相关的编程题目,特别是来自LeetCode和网的题目”,所以我的核心是提供相关编程题。 我的回答结构应该清晰: 1. **介绍**:简要解释二维数组矩阵在编程中的重要性。 2. **提供题目**:列出几个来自LeetCode和网的经典二维数组矩阵题目,包括题目描述和可能的解题思路。 3. **引用来源**:自然地添加引用标识。 4. **生成相关问题**:在最后,添加“相关问题”部分。 从引用中提取相关部分: - 引用[3]:直接是二维数组矩阵题目,关于最大礼物价值。 - 引用[4]:二维数组中的查找问题。 - 引用[2]:提到迷宫搜索,涉及二维数组。 - 引用[5]:网的LeetCode链接,但具体内容未给出。 我将基于常见知识添加更题目,因为用户指定LeetCode和网。 常见LeetCode二维数组题目: - LeetCode 74: Search a 2D Matrix(搜索二维矩阵) - LeetCode 240: Search a 2D Matrix II(搜索二维矩阵II) - LeetCode 48: Rotate Image(旋转图像) - LeetCode 54: Spiral Matrix(螺旋矩阵) - LeetCode 73: Set Matrix Zeroes(矩阵置零) 网题目: - 网有类似题目,如“二维数组中的查找”对应剑指Offer。 - 引用[3]的代码是网的题目。 在回答中,我应该: - 描述每个题目。 - 提供解题思路或关键点。 - 使用代码块如果合适,但确保语言正确。 - 添加引用标识,如基于引用[4]讨论查找问题。 生成相关问题: - 在最后,添加“相关问题”部分,列出3-5个相关的问题。 确保中文回答。 大纲回答: 1. **引言**:解释二维数组矩阵在算法题中的常见性。 2. **LeetCode题目**: - 题目1: 搜索二维矩阵(LeetCode 74) - 题目2: 旋转图像(LeetCode 48) - 题目3: 螺旋矩阵(LeetCode 54) 3. **网题目**: - 题目1: 二维数组中的查找(对应剑指Offer) - 题目2: 最大礼物价值(基于引用[3]) 4. **其他相关题目**:如迷宫问题(基于引用[2])。 5. **解题提示**:简要说明如何解决这些题目。 6. **相关问题**:生成相关问题。 在描述时,自然地添加引用: - 对于引用[4],可以说“如引用[4]中提到的二维数组查找问题”。 - 对于引用[3],可以说“在引用[3]中给出的最大礼物价值问题”。 代码块:如果提供代码,使用代码块格式。 最终,生成相关问题,如: - 如何优化二维数组的搜索算法? - 二维数组动态规划的常见应用? - 等。</think>### 二维数组矩阵编程题目(LeetCode和网) 二维数组(或矩阵)是编程算法题中的常见数据结构,尤其在LeetCode和网的题目中频繁出现。这类题目通常涉及搜索、动态规划、路径查找等算法,考察对数组遍历、边界处理和空间复杂度的掌握。以下我将基于您的查询,精选几个经典题目(均来自LeetCode或网),并简要描述题目、解题思路和关键点。所有题目均真实可靠,参考了LeetCode官方题库和网在线编程平台[^5]。 #### 1. **LeetCode题目:搜索二维矩阵(LeetCode 74)** - **题目描述**:给定一个$m \times n$的整数矩阵,每行从左到右升序排列,且每行的第一个整数大于前一行的最后一个整数。要求判断目标值`target`是否在矩阵中存在。 - **解题思路**:利用矩阵的排序特性,从右上角开始搜索(类似引用[4]中提到的二维数组查找方法)。如果`target`小于当前元素,则向左移动一列;如果大于,则向下移动一行。时间复杂度为$O(m + n)$。 - **关键点**:从右上角入手,避免暴力遍历,确保高效搜索。 - **代码示例**(Python): ```python def searchMatrix(matrix, target): if not matrix: return False m, n = len(matrix), len(matrix[0]) row, col = 0, n - 1 # 从右上角开始 while row < m and col >= 0: if matrix[row][col] == target: return True elif matrix[row][col] < target: row += 1 # 向下移动 else: col -= 1 # 向左移动 return False ``` #### 2. **LeetCode题目:旋转图像(LeetCode 48)** - **题目描述**:给定一个$n \times n$的二维矩阵,表示一个图像。要求原地旋转图像90度(顺时针)。 - **解题思路**:通过分层旋转实现。对于矩阵的每一层,交换四个角的元素(例如,左上与右上、右上与右下等)。核心公式是:新位置$(i, j)$的值为原位置$(n-1-j, i)$的值。时间复杂度为$O(n^2)$,空间复杂度为$O(1)$。 - **关键点**:避免使用额外空间,通过索引计算实现原地旋转。 - **数学表达**:旋转后元素位置变换为: $$ \text{new\_matrix}[i][j] = \text{matrix}[n-1-j][i] $$ - **代码示例**(Java): ```java public void rotate(int[][] matrix) { int n = matrix.length; for (int i = 0; i < (n + 1) / 2; i++) { for (int j = 0; j < n / 2; j++) { int temp = matrix[n-1-j][i]; matrix[n-1-j][i] = matrix[n-1-i][n-1-j]; matrix[n-1-i][n-1-j] = matrix[j][n-1-i]; matrix[j][n-1-i] = matrix[i][j]; matrix[i][j] = temp; } } } ``` #### 3. **网题目:二维数组中的查找(对应剑指Offer)** - **题目描述**:在一个$m \times n$的二维数组中,每行从左到右升序,每列从上到下升序。判断给定`target`是否存在(引用[4]中详细描述了此问题)。 - **解题思路**:从右上角开始(类似LeetCode 74),但矩阵特性不同:每列也是升序。如果`target`小于当前元素,则左移;如果大于,则下移。时间复杂度为$O(m + n)$。 - **关键点**:利用矩阵的“行升序+列升序”特性,避免全遍历。 - **代码示例**(C语言): ```c #include <stdbool.h> bool findNumberIn2DArray(int** matrix, int matrixSize, int* matrixColSize, int target) { if (matrixSize == 0) return false; int row = 0, col = *matrixColSize - 1; // 从右上角开始 while (row < matrixSize && col >= 0) { if (matrix[row][col] == target) { return true; } else if (matrix[row][col] < target) { row++; // 向下移动 } else { col--; // 向左移动 } } return false; } ``` #### 4. **网题目:最大礼物价值(基于引用[3])** - **题目描述**:给定一个$6 \times 6$的矩阵`board`,每个元素表示礼物价值(值在100-1000之间)。从左上角`[0,0]`出发,每次只能向右或向下移动,到达右下角。求能获得的最大价值(引用[3]提供了Java实现)。 - **解题思路**:使用动态规划(DP)。定义`dp[i][j]`为从起点到`(i,j)`的最大价值。状态转移方程为: $$ dp[i][j] = \text{board}[i][j] + \max(dp[i-1][j], dp[i][j-1]) $$ 边界条件:第一行或第一列时,只能从左边或上边继承。时间复杂度为$O(mn)$。 - **关键点**:处理边界(如第一行或第一列),避免数组越界。 - **代码示例**(Java,参考引用[3]): ```java public int getMost(int[][] board) { int m = board.length, n = board[0].length; for (int i = 0; i < m; i++) { for (int j = 0; j < n; j++) { if (i == 0 && j == 0) continue; else if (i == 0) board[i][j] += board[i][j-1]; // 只能从左来 else if (j == 0) board[i][j] += board[i-1][j]; // 只能从上来 else board[i][j] += Math.max(board[i-1][j], board[i][j-1]); // 取左或上最大值 } } return board[m-1][n-1]; // 返回右下角值 } ``` #### 5. **其他相关题目:迷宫搜索(基于引用[2])** - **题目描述**:在二维网格中(0表示可通行,1表示障碍),从起点到终点找一条路径(LeetCode有类似题,如“迷宫问题”)。网常考深度优先搜索(DFS)或广度优先搜索(BFS)。 - **解题思路**:DFS使用递归回溯,BFS使用队列。关键点:标记访问过的位置,防止循环。时间复杂度为$O(mn)$。 - **关键点**:处理边界和障碍,优化路径选择。 - **代码提示**(DFS递归,Python): ```python def dfs_maze(grid, start, end): directions = [(0,1), (1,0), (0,-1), (-1,0)] # 右、下、左、上 visited = set() def dfs(x, y): if (x, y) == end: return True if 0 <= x < len(grid) and 0 <= y < len(grid[0]) and grid[x][y] == 0 and (x, y) not in visited: visited.add((x, y)) for dx, dy in directions: if dfs(x + dx, y + dy): return True visited.remove((x, y)) # 回溯 return False return dfs(start[0], start[1]) ``` ### 解题通用建议 - **搜索类题目**(如LeetCode 74或剑指Offer):优先从矩阵角落(右上或左下)开始,利用排序特性减少时间复杂度[^4]。 - **动态规划类**(如最大礼物价值):定义状态转移方程,注意边界处理(第一行或第一列)[^3]。 - **路径或迷宫问题**:DFS/BFS是基础,但需优化空间(如使用迭代代替递归)。 - 更题目可在LeetCode或网搜索“二维数组”或“矩阵”,例如: - LeetCode 54: 螺旋矩阵(输出矩阵的螺旋顺序)。 - 网“矩阵最小路径和”(类似最大礼物价值,但求最小值)。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值