牛客多校训练第二场E——MAZE(DP+线段树)

链接:https://ac.nowcoder.com/acm/contest/882/E
来源:牛客网
 

时间限制:C/C++ 5秒,其他语言10秒
空间限制:C/C++ 262144K,其他语言524288K
64bit IO Format: %lld

题目描述

Given a maze with N rows and M columns, where bijbij represents the cell on the i-row, j-th column. If bi,j="1"bi,j="1", it's a wall and can't not be passed. If you are on the cell bi,jbi,j, you can go to b(i+1),jb(i+1),j, bi,(j−1)bi,(j−1), or bi,(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.

输入描述:

The first line of input contains three space-separated integers N, M, Q.
Following N lines each contains M characters bijbij representing the maze.
Following Q lines each contains three space-separated integers qi,ai,biqi,ai,bi.

If qi=1qi=1, the state of cell bai,bibai,bi is changed.
If qi=2qi=2, you need to find out the number of way to start at cell b1,aib1,ai and finish at cell bN,bibN,bi.

1≤N,Q≤500001≤N,Q≤50000
1≤M≤101≤M≤10
bij∈"01"bij∈"01"
1≤qi≤21≤qi≤2
If qi=1qi=1, 1≤ai≤N1≤ai≤N and 1≤bi≤M1≤bi≤M.
If qi=2qi=2, 1≤ai,bi≤M1≤ai,bi≤M.

输出描述:

For each qi=2qi=2, Output one line containing an integer representing the answer module 109+7(1000000007)109+7(1000000007).

示例1

输入

复制

2 2 3
00
00
2 1 2
1 1 2
2 1 2

输出

复制

2
1

题目大意:给你一个n*m的矩阵地图,在地图中1表示障碍物,0表示通道,每次移动可以从当前点向左,向右,向下移动,现在有两种操作:

1.将地图上的点x,y翻转(0变成1,1变成0)

2.给你第一行的一个起点点x1,y1,第n行的一个终点点xn,yn,问你从起点到终点有多少种方案,答案对mod=1e9+7取模

思路:

参考于大神博客:点我看大神博客

考虑没有修改的情况,那就是一个简单的棋盘dp,设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] 的和,表示可以从这些点转移过来

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][5]=dp[1][4]+dp[1][5]

则可以对应于矩阵的乘法,比如上图,从第一行到第二行的转移矩阵如下

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,不能为0

比如,dp[2][2]=dp[1][1]+dp[1][2]+dp[1][3],那么在矩阵中第二列前三个为1,后三个为0

好了,现在有了转移矩阵,我们要求从 (1, x) 走到 (n, y) 的方案数,即令 dp[1][x] = 1,求 dp[n + 1][y]​​​​​​​

则可以用矩阵表示,ans=Π M1 ~ Mn​​​​​​​(连乘),取ans[x][y]即可。

到这里,静态的问题得到解决,那么如果有修改操作呢?注意修改一个点,一行的转移矩阵都要改变

所以我们可以用线段树维护1~n行的所有转移矩阵的区间乘值,如果有修改,则对于线段树而言是单点修改,如果需要查询答案,直接查询1到n的乘积矩阵即可

矩阵乘法O(m^3),线段树建树O(n),查询O(logn),修改的时候需要乘法,所以最后时间复杂度为O(m^3logn)

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#define int long long
using namespace std;
const int mod=1e9+7;
const int maxn=50000+10;
struct Mat{
    int m[12][12];
    void set_E(){
        for(int i=0;i<12;i++){
            for(int j=0;j<12;j++){
                m[i][j]=i==j;
            }
        }
    }

};
//线段树维护的节点
struct Node{
    int l,r;
    Mat M;
    void init(){
        for(int i=0;i<12;i++){
            for(int j=0;j<12;j++){
                M.m[i][j]=0;
            }
        }
    }
}Tree[maxn<<2];
int n,m,q;
int mp[maxn][12];

int add(int a, int b){
    return a + b >= mod ? a + b - mod : a + b;
}

int mul(int a, int b){
    return a * b >= mod ? a * b % mod : a * b;
}


Mat Mmul(Mat a, Mat b){
    Mat 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] = add(c.m[i][j], mul(a.m[i][k], b.m[k][j]));
    }
    return c;
}

void pushup(int root){
    Tree[root].M=Mmul(Tree[root<<1].M,Tree[root<<1|1].M);
}

void build(int root,int l,int r){
    //puts("ok");
    Tree[root].l=l,Tree[root].r=r;
    if(l==r){
        Tree[root].init();
        for(int i=1;i<=m;i++){
            for(int j=i;j<=m;j++){
                if(mp[l][j]) break;
                Tree[root].M.m[j][i]=1;
            }
            for(int j=i;j;j--){
                if(mp[l][j]) break;
                Tree[root].M.m[j][i]=1;
            }
        }
        return;
    }
    int mid=(l+r)>>1;
    build(root<<1,l,mid);
    build(root<<1|1,mid+1,r);
    pushup(root);
}

void Update(int root,int pos){
    if(Tree[root].l==Tree[root].r&&Tree[root].l==pos){
        Tree[root].init();
        for(int i=1;i<=m;i++){
            for(int j=i;j<=m;j++){
                if(mp[Tree[root].l][j]) break;
                Tree[root].M.m[j][i]=1;
            }
            for(int j=i;j;j--){
                if(mp[Tree[root].l][j]) break;
                Tree[root].M.m[j][i]=1;
            }
        }
        return ;
    }
    int mid=(Tree[root].l+Tree[root].r)>>1;
    if(pos<=mid)Update(root<<1,pos);
    else Update(root<<1|1,pos);
    pushup(root);
}
Mat query(int root,int l,int r){
    if(Tree[root].l>=l&&Tree[root].r<=r){
        return Tree[root].M;
    }
    int mid=(Tree[root].l+Tree[root].r)>>1;
    Mat ans;
    ans.set_E();
    if(l<=mid){
        ans=Mmul(ans,query(root<<1,l,r));
    }
    if(r>mid){
        ans=Mmul(ans,query(root<<1|1,l,r));
    }
    return ans;
}
char s[12];
signed main(){
    scanf("%lld%lld%lld",&n,&m,&q);
    for(int i=1;i<=n;i++){
        scanf("%s",s);
        for(int j=0;j

 

你可能感兴趣的:(线段树)