题意:给定一个大小为n*m( n < = 5 e 4 , m < = 10 n<=5e4,m<=10 n<=5e4,m<=10)的矩阵,其中只有0,1元素,0表示该点可达,1表示不可达,接下来q( q < = 5 e 4 q<=5e4 q<=5e4)次操作:
- 可以选择一个格子翻转
- 询问从(1,x)->(n,y)的方案数,每次只能向下、左、右行走,输出答案取模 1 e 9 + 7 1e9+7 1e9+7
线段树上维护矩阵乘法,合并左右子树保证了路径不会回头。
每个线段树的节点上维护一个系数矩阵dp[i][j]。比如线段树上节点表示行号1~5,那么系数矩阵dp[i][j]就表示从(1,i)->(5,j)的方案数。
合并左右子树时做一个矩阵乘法,dp[i][j]=dp[i][k]*dp[k][j]即可。
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn=5e4+7;
const int mod=1e9+7;
int n,m;
struct Tree{
ll a[12][12];
}tree[maxn<<2|1];
Tree mutil(Tree a,Tree b){
Tree t;
memset(t.a,0,sizeof(t.a));
for(int i=1;i<=m;++i)
for(int j=1;j<=m;++j)
for(int k=1;k<=m;++k)
t.a[i][j]=(t.a[i][j]+a.a[i][k]*b.a[k][j])%mod;
return t;
}
char s[maxn][19];
void build(int l,int r,int k){
if(l==r){
for(int i=1;i<=m;++i)
for(int j=i;j<=m;++j){
bool f=1;
int z=i;
while(z<=j){
if(s[l][z]=='1') f=0;
++z;
}
tree[k].a[i][j]=tree[k].a[j][i]=f;
}
return ;
}
int mid=(l+r)>>1;
build(l,mid,k<<1);
build(mid+1,r,k<<1|1);
tree[k]=mutil(tree[k<<1],tree[k<<1|1]);
}
void updata(int l,int r,int k,int x,int y){
if(l==r){
if(s[x][y]=='0') s[x][y]='1';
else s[x][y]='0';
for(int i=1;i<=m;++i)
for(int j=i;j<=m;++j){
int z=i;
bool f=1;
while(z<=j){
if(s[l][z]=='1') f=0;
++z;
}
tree[k].a[i][j]=tree[k].a[j][i]=f;
}
return ;
}
int mid=(l+r)>>1;
if(x<=mid) updata(l,mid,k<<1,x,y);
else updata(mid+1,r,k<<1|1,x,y);
tree[k]=mutil(tree[k<<1],tree[k<<1|1]);
}
int main(){
int q,id,l,r;
scanf("%d%d%d",&n,&m,&q);
for(int i=1;i<=n;++i) scanf("%s",s[i]+1);
build(1,n,1);
while(q--){
scanf("%d%d%d",&id,&l,&r);
if(id==1) updata(1,n,1,l,r);
else printf("%lld\n",tree[1].a[l][r]);
}
return 0;
}