题干:
链接:https://ac.nowcoder.com/acm/contest/882/E?&headNav=acm
来源:牛客网
Given a maze with N rows and M columns, where bijb_{ij}bij represents the cell on the i-row, j-th column. If bi,j="1"b_{i, j} = \texttt{"1"}bi,j="1", it's a wall and can't not be passed. If you are on the cell bi,jb_{i, j}bi,j, you can go to b(i+1),jb_{(i+1), j}b(i+1),j, bi,(j−1)b_{i, (j-1)}bi,(j−1), or bi,(j+1)b_{i, (j+1)}bi,(j+1) as long as it's not a wall.
Sometime, a cell may be changed into wall, or vise versa. You need to find out the number of way to pass through the maze starting at some given cell and finishing at some given cell.
If the starting cell or finishing cell is a wall, there's clearly no way to pass through the maze.
Note that you can't go back to the cell you just from.
示例1
输入
复制
2 2 3
00
00
2 1 2
1 1 2
2 1 2
输出
复制
2
1
题目大意:
有一个 n*m 的 01 矩阵,1 表示不可行,0 代表可行;
每次可以从 (i, j) 走到 (i, j – 1),(i, j + 1) 和 (i + 1, j),且不能回到已走过的格子;
有 q 个以下两种操作:
1、将某个格子的状态反转,即1变0,0变1
2、询问从 (1, x) 走到 (n, y) 的方案数。
1 <= n,q <= 5e4,1 <= m <= 10。
解题报告:
先考虑不带修改的版本,设矩阵第 i 行第 j 个元素为 A[i][j];
dp[i][j] 表示从第 i – 1 行经过 (i – 1, j) 走到 (i, j) 的方案数,状态转移方程如下:
简单来说,dp[i][j] 等于 A[i – 1, j] 向左和向右 A[i – 1][k] 都等于 0 的那些 dp[i – 1][k] 的和;
如当 n = 2,m = 6 时:(最上面为第一行)
0 | 0 | 0 | 1 | 0 | 0 |
1 | 0 | 1 | 0 | 1 | 0 |
dp[2][2] = dp[1][1] + dp[1][2] + dp[1][3];
dp[2][4] = 0
dp[2][6] = dp[1][5] + dp[1][6]。
dp[2]的其余值都是0。
因此第 i 行的 dp 值到第 i + 1 行的 dp 值的转移可用矩阵 Mi 实现;
如上图第一行的 dp 值到第二行 dp 值的转移矩阵 M1:
(注意就算是a[i][j]==1,但是也要更新这一个值,但是其实并没有什么用,因为后面也会被continue掉)
1 | 1 | 1 | 0 | 0 | 0 |
1 | 1 | 1 | 0 | 0 | 0 |
1 | 1 | 1 | 0 | 0 | 0 |
0 | 0 | 0 | 0 | 0 | 0 |
0 | 0 | 0 | 0 | 1 | 1 |
0 | 0 | 0 | 0 | 1 | 1 |
那么要求从 (1, x) 走到 (n, y) 的方案数,即令 dp[1][x] = 1,求 dp[n + 1][y];
为什么是 dp[n + 1][y] 而不是 dp[n][y],因为 dp[n][y] 设定状态的时候,表示的就是从第 n – 1 行经过 (n – 1, y) 走到 (n, y) 的方案数,故意的漏掉那些从第 n 行走到 (n, y) 的方案数,这样做的目的也是便于直接统计,而 dp[n + 1][y] 则表示从第 n 行经过 (n, y) 的所有方案数,也就是我们要的答案了。
那么让矩阵M[i][j]代表的含义是从第i列走到第j列的方案数。
若令矩阵,则就是答案。
再考虑带修改的版本;
因为每次都会修改一个点,也就是每次只会修改N个矩阵中的一个矩阵,然后再求矩阵的和。
因为矩阵乘法具有可交换性质,所以可以用线段树维护矩阵乘积:,反转 (x, y) 的操作即重构 ,为单点修改,时间复杂度为 O(m^3logn);
根节点维护的就是 ,可 O(1) 回答询问,总时间复杂度 O(m^3logn)。
注意实现的细节,你如果在结构体中加上l和r节点的话,那矩阵最好重新定义一个结构体,不然的话你pushup就要多写两行。如果不多这几行的话显然是不对的。(因为你的l和r就被冲掉了)
再贴一个别人的题解:
这样的话就可以理解为,对于每一个查询只有dp[1][x]是1,dp[1]的其他值都是0,然后求dp[n+1][y],
所以就是=(0,0,,,第x位上是1,,0) * 构造的矩阵的第四列,也就是构造的矩阵的M[x][y]项。
AC代码:
#include<cstdio>
#include<iostream>
#include<algorithm>
#include<queue>
#include<map>
#include<vector>
#include<set>
#include<string>
#include<cmath>
#include<cstring>
#define FF first
#define SS second
#define ll long long
#define pb push_back
#define pm make_pair
using namespace std;
typedef pair<int,int> PII;
const int MAX = 50005 + 5;
const ll mod = 1e9 + 7;
int n,m,q;
struct TREE {int l,r;ll m[12][12];
} tr[MAX<<2];
int a[MAX][13];
TREE mul(TREE a,TREE b) {TREE c;for(int i = 1; i<=m; i++) {for(int j = 1; j<=m; j++) {c.m[i][j] = 0;for(int k = 1; k<=m; k++) {c.m[i][j] = (c.m[i][j] + (a.m[i][k]*b.m[k][j])%mod) % mod;}}}return c;
}
void modify(int cur,int x) {memset(tr[cur].m,0,sizeof tr[cur].m);for(int i = 1; i<=m; i++) {if(a[x][i] == 1) continue;tr[cur].m[i][i]=1;for(int j = i-1; j>=1&&a[x][j]==0; j--) tr[cur].m[i][j] = 1;for(int j = i+1; j<=m&&a[x][j]==0; j++) tr[cur].m[i][j] = 1;}
}void pushup(int cur) {int l = tr[cur].l,r = tr[cur].r;tr[cur] = mul(tr[cur*2],tr[cur*2+1]);tr[cur].l = l,tr[cur].r = r;
}
void build(int l,int r,int cur) {tr[cur].l = l,tr[cur].r = r;if(l == r) {modify(cur,l);return;}int m = (l+r)>>1;build(l,m,cur*2);build(m+1,r,cur*2+1);pushup(cur);
}
void update(int tar,int cur) {if(tr[cur].l == tr[cur].r) {modify(cur,tr[cur].l); return;}int mid = (tr[cur].l + tr[cur].r)>>1;if(tar <= mid) update(tar,cur*2);else update(tar,cur*2+1);pushup(cur);
}
int main()
{cin>>n>>m>>q;for(int i = 1; i<=n; i++) {for(int j = 1; j<=m; j++) scanf("%1d",&a[i][j]);}build(1,n,1);while(q--) {int op,x,y;scanf("%d%d%d",&op,&x,&y);if(op == 1) a[x][y] ^= 1,update(x,1);else printf("%lld\n",tr[1].m[x][y]%mod);}return 0 ;
}