高斯消元模板及习题
高斯消元
模板
1. 实数高斯消元
其中 \(a\) 表示系数矩阵, \(b\) 表示常数项, \(f\) 表示最终答案。
当然 \(b_i\) 可以用 \(a_{i,n+1}\) 来表示
void gauss(int r){
for(int i=1;i<=r;++i){
int loc=i;
for(int j=i+1;j<=r;++j)
if(fabs(a[j][i])>fabs(a[loc][i]))loc=j;
if(fabs(a[i][i])<EPS){return puts("No solution"),void();}
swap(a[i],a[loc]);swap(b[i],b[loc]);//别忘了b数组
for(int k=i+1;k<=r;++k){
double bas=a[k][i]/a[i][i];
b[k]-=bas*b[i];
for(int j=i;j<=r;++j)
a[k][j]-=bas*a[i][j];
}
}
for(int i=r;i>=1;--i){//回代
for(int j=i+1;j<=r;++j)
b[i]-=f[j]*a[i][j];
f[i]=b[i]/a[i][i];
}
}
2. 异或高斯消元
满足
void guass(){//异或高斯消元成上三角矩阵
for(int i=1;i<=n;++i){
int loc=i;
while(loc<=n&&!a[loc][i])++loc;
if(loc>n)continue;//无唯一解
swap(a[loc],a[i]),swap(b[loc],b[i]);
for(int k=1;k<=n;++k){
if(i==k||!a[k][i])continue;
for(int j=i;j<=n;++j)
a[k][j]^=a[i][j];
b[k]^=b[i];
}
}
//回代省略,和下面相同bitset的回带相同
}
void dfs(int lef,int num){//枚举自由元,从下往上,(可以理解为回代的过程)
if(num>=ans)return;
if(lef==0){
ans=num;
return;
}
if(a[lef][lef]){//状态确定(非自由元)
bool cnt=b[lef];
for(int i=lef+1;i<=n;++i)
if(a[lef][i])cnt^=sta[i];//默认全关
dfs(lef-1,num+cnt);
}else{//枚举状态
sta[lef]=0;dfs(lef-1,num);
sta[lef]=1;dfs(lef-1,num+1);sta[lef]=0;
}
}
2.1 bitset优化01矩阵高斯消元
void gauss(int r){//01矩阵,bitset 优化
for(int i=1;i<=r;++i){
int loc=i;
while(loc<=r&&!a[loc][i])++loc;
if(a[loc][i])swap(a[loc],a[i]);
for(int j=i+1;j<=r;++j)
if(a[j][i])a[j]^=a[i];
}
for(int i=r;i>=1;--i){
if(!a[i][i]){ans[i]=1;continue;}//如果这一位置的值不确定,随便填个1
for(int j=i+1;j<=r;++j)
ans[i]^=(ans[j]*a[i][j]);
ans[i]^=a[i][r+1];
}
}
3. 行列式求值
常用于矩阵树定理,这里是模意义下的
ll gauss(int r){
ll f=1,tmp=1;
for(int i=1;i<=r;++i){//转化成上三角矩阵
for(int k=i+1;k<=r;++k){
while(a[i][i]){//辗转相除
ll tim=a[k][i]/a[i][i];
for(int j=i;j<=r;++j)
a[k][j]=(a[k][j]-tim*a[i][j]%MOD+MOD)%MOD;
swap(a[k],a[i]);f=-f;
}swap(a[k],a[i]);f=-f;
}
}
for(int i=1;i<=r;++i)
tmp=tmp*a[i][i]%MOD;
tmp*=f;
return (tmp+MOD)%MOD;
}
应用
1.解决一类有后效性的DP问题
1.1 图上的随机游走问题
1.1.1 LG3232 [HNOI2013] 游走
给定一个 \(n\) 个点 \(m\) 条边的无向连通图,顶点从 \(1\) 编号到 \(n\),边从 \(1\) 编号到 \(m\)。
小 Z 在该图上进行随机游走,初始时小 Z 在 \(1\) 号顶点,每一步小 Z 以相等的概率随机选择当前顶点的某条边,沿着这条边走到下一个顶点,获得等于这条边的编号的分数。当小 Z 到达 \(n\) 号顶点时游走结束,总分为所有获得的分数之和。 现在,请你对这 \(m\) 条边进行编号,使得小 Z 获得的总分的期望值最小。
设 \(deg_i\) 表示第 \(i\) 个店的度数,\(f_i\) 表示地 \(i\) 个点期望经过的次数。得到转移
由于到 \(n\) 点时就停止游走,所以不考虑 \(n\) 点的贡献,对 \(n-1\) 个 \(f_i\) 高斯消元求解。
设 \(g_i\) 表示第 \(i\) 条边期望经过的次数。
排序,期望越大的边标号越小。
int n,m,uu[M],vv[M],deg[N];
double a[N][N],b[N],f[N],g[M],ans;////g数组开小了,发生了奇怪的内存泄漏
void guass(int r){
for(int i=1;i<=r;++i){
int loc=i;
for(int j=i+1;j<=r;++j)
if(fabs(a[j][i])>fabs(a[loc][i]))loc=j;
swap(a[i],a[loc]);swap(b[i],b[loc]);//别忘了b数组
for(int k=i+1;k<=r;++k){
double bas=a[k][i]/a[i][i];
b[k]-=bas*b[i];
for(int j=i;j<=r;++j)
a[k][j]-=bas*a[i][j];
}
}
for(int i=r;i>=1;--i){//回代
for(int j=i+1;j<=r;++j)
b[i]-=f[j]*a[i][j];
f[i]=b[i]/a[i][i];
}
}
int main(){
n=read(),m=read();
for(int i=1;i<=m;++i){
uu[i]=read(),vv[i]=read();
addline(uu[i],vv[i]);addline(vv[i],uu[i]);
++deg[uu[i]],++deg[vv[i]];
}
for(int u=1;u<n;++u){
a[u][u]=1.0;
for(int i=head[u];i;i=enxt[i]){
int v=to[i];
if(v!=n)a[u][v]=-1.0/deg[v];
}
}b[1]=1.0;
guass(n-1);
for(int i=1;i<=m;++i){
int u=uu[i],v=vv[i];
if(u!=n)g[i]+=f[u]/deg[u];
if(v!=n)g[i]+=f[v]/deg[v];
}
sort(g+1,g+1+m);
for(int i=1;i<=m;++i)
ans+=g[i]*(double)(m-i+1);
printf("%.3lf\n",ans);
return 0;
}
1.1.2 LG3211 [HNOI2011]XOR和路径
给定一张无向图,一条路径的权值为路径上边权的异或和。在每个点等概率地走向相邻的店,求 \(1\to n\) 路径权值的期望。(有自环重边)
考虑对二进制数进行拆位,每一位都是独立的,考虑每位的概率。
设 \(f_u\) 为 \(u\to n\) 的路径这一位为 \(1\) 的概率,\(deg_u\) 表示 \(u\) 的出度,假设存在 \(v\) 和 \(u\) 之间有边。则
写成这个样子构造出高斯消元的矩阵大概就不难了。
最后答案 \(\sum_{i}2^if_i\)
void guass(int r){
for(int i=1;i<=r;++i){
int loc=i;
for(int j=i+1;j<=r;++j)
if(fabs(a[j][i])>fabs(a[loc][i]))loc=j;
swap(a[loc],a[i]),swap(b[loc],b[i]);
for(int k=i+1;k<=r;++k){
double bas=a[k][i]/a[i][i];
b[k]-=bas*b[i];
for(int j=i;j<=r;++j)
a[k][j]-=bas*a[i][j];
}
}
for(int i=r;i>=1;--i){
for(int j=i+1;j<=r;++j)
b[i]-=f[j]*a[i][j];
f[i]=b[i]/a[i][i];
}
}
int main(){
n=read(),m=read();
for(int i=1;i<=m;++i){
int u=read(),v=read(),w=read();
if(u==v){
++deg[u];
addline(u,u,w);
}else{
++deg[u],++deg[v];
addline(u,v,w);addline(v,u,w);
}
}
for(int w=0;w<=30;++w){
memset(a,0,sizeof(a));
memset(b,0,sizeof(b));
a[n][n]=-1.0;
for(int u=1;u<n;++u){
a[u][u]=-1.0;
for(int i=head[u];i;i=enxt[i]){
int v=to[i],wei=weight[i];
if((wei>>w)&1)
a[u][v]-=1.0/deg[u],b[u]-=1.0/deg[u];
else a[u][v]+=1.0/deg[u];
}
}
guass(n);
ans+=f[1]*(1<<w);
}
printf("%.3lf\n",ans);
return 0;
}
1.1.3 LG2973 [USACO10HOL]Driving Out the Piggies G
一个无向图,节点 1 有一个炸弹,在每个单位时间内,有p/q的概率在这个节点炸掉,有1-p/q的概率随机选择一条出去的路到其他的节点上。问最终炸弹在每个节点上爆炸的概率。
定义 \(f_i\) 为第 \(i\) 个点的期望经过次数,这个答案有 \(f_i\times p/q\) 的记录在这里爆炸。
高斯消元即可。
1.2 特殊矩阵的优化
1.2.1 CF24D Broken robot
\(n\) 行 \(m\) 列的矩阵,现在在 \((x,y)\),每次等概率向左,右,下走或原地不动,但不能走出去,问走到最后一行期望的步数。
注意,\((1,1)\) 是木板的左上角,\((n,m)\) 是木板的右下角。
\(1\leq n,m\leq 10^3\)
定义 \(f_{i,j}\) 为从第 \(i\) 行 \(j\) 列走到最后一行的步数的期望值,\(f_{x,y}\) 即为答案。
按照套路,可以得到转移。
其实可以发现 \(f_{i,j}\) 和 \(f_{i+1,j}\) 之间是没有后效性的,而 \(f_{i,j}\) 和 \(f_{i,j+1}\) 之间是有后效性的。也就是说每一行之间可以像 \(dp\) 一样直接转移一些状态,而每一行的各个状态需要高斯消元具体求解。
说得很不好。其实就是第 \(i\) 每一列的 \(m\) 个状态列 \(m\) 个方程(见上方式子),这些方程中 \(f_{i+1,j}\) 是已经求得了的,而其他的则作为未知量。直接这样做的复杂度是 \(O(nm^3)\) 的。
然后你会发现矩阵长这个样子。
每行至多三个元素,而且分布在主对角线上。这样的话高斯消元的时候只用消下面一行的三个元素即可。所以消元的时间复杂度就降成了 \(O(m)\)。
总时间复杂度 \(O(nm)\)。
记得特判 \(m=1\) 哦!
自认为很优雅的代码
const int N=1003;
int n,m,x,y;
double ans[N],a[N][N],b[N];
inline void buildit(){
a[1][1]=2.0/3,a[1][2]=-1.0/3,b[1]=ans[1]/3+1;
for(int i=2;i<m;++i)
a[i][i-1]=-1.0/4,a[i][i]=3.0/4,a[i][i+1]=-1.0/4,b[i]=ans[i]/4+1;
a[m][m-1]=-1.0/3,a[m][m]=2.0/3,b[m]=ans[m]/3+1;
for(int i=1;i<=m;++i)ans[i]=0;
}
inline void sp_guass(){
for(int i=1;i<=m;++i){
double bas=a[i+1][i]/a[i][i];
b[i+1]-=bas*b[i];
for(int j=i;j<=i+2;++j)
a[i+1][j]-=bas*a[i][j];
}
for(int i=m;i>=1;--i){
b[i]-=ans[i+1]*a[i][i+1];
ans[i]=b[i]/a[i][i];
}
}
int main(){
n=read(),m=read(),x=read(),y=read();
if(m==1)return printf("%.8lf\n",2.0*(n-x)),0;
for(int i=n-1;i>=x;--i){
buildit();
sp_guass();
}
printf("%.8lf\n",ans[y]);
return 0;
}
1.2.2 LG4457 [BJOI2018]治疗之雨
你现在有 \(m+1\) 个数:第一个为 \(p\),最小值为 \(0\),最大值为 \(n\);剩下 \(m\)个都是无穷,没有最小值或最大值。你可以进行任意多轮操作,每轮操作如下:
在不为最大值的数中等概率随机选择一个(如果没有则不操作),把它加一;
进行 \(k\)次这个步骤:在不为最小值的数中等概率随机选择一个(如果没有则不操作),把它减一。
现在问期望进行多少轮操作以后第一个数会变为最小值 \(0\)。
对于 \(100\%\) 的数据, \(1 \leq T \leq 100\),\(1 \leq p \leq n \leq 1500\) ,\(0 \leq m, k \leq 1000000000\)。
\(p_i\) 表示一回合扣 \(i\) 滴血的概率,则
把 \(k\) 次扣血看成一个长为 \(k\) 序列,每个序列有 \(m+1\) 个选择方法,总的选择方法是 \((m+1)^k\),而我们要从中选出 \(i\) 个位置,让这些位置队第一个数扣血,所以选择方案是 \(C_{k}^{i}\) 的,那么其他的位置共 \(m\) 个,他们随便选择的方案就是 \(m^{k-i}\)。
\(p_i\) 可以递推得到。
定义 \(f_i\) 为血量为 \(i\) 时被干掉的期望回合数。则分成这回合加血和不加血两部分来转移。同时这回合加血的时候可能不会扣血,所以 \(f_{i+1}\) 也能转移到 \(f_i\)。
\(\sum\) 内表示在被加血的情况下掉 \(j+1\) 滴血和不被加血的情况下掉 \(j\) 滴血。同时还有 \(p_0\times \frac{1}{m+1}\) 的概率不扣血加 \(1\) 滴血。
\(f_n\) 不能加血。那么
然后我们就可以使用高斯消元 \(O(n^3)\),无法通过此题。
这个高斯消元的过程实际上可以优化。
容易发现 \(f_i\) 只能从 \(f_{0\sim i+1}\) 转移,
所以除了最后一行,其他每一行在主对角线右侧只有一个元素,这样我们在第 \(i\) 行消元的时候可以只消掉第 \(i\) 列,这样实际消元的时候每一行只有 \(a_{i,i},a_{i,i+1}\) 两元素有值,而且最后一列只剩下一项,然后可以回带解出前面的所有答案。 时间复杂度 \(O(n^2)\)。具体见代码。
const int N=1503;
ll n,p,m,k,P[N],a[N][N];
void sp_gauss(){
for(int i=1;i<=n;++i){
ll tmp=fpr(a[i][i]);a[i][i]=1;
a[i][n+1]=a[i][n+1]*tmp%MOD;
if(i!=n)a[i][i+1]=a[i][i+1]*tmp%MOD;
for(int j=i+1;j<=n;++j){
ll ttf=a[j][i];a[j][i]=0;
a[j][i+1]=(a[j][i+1]-ttf*a[i][i+1]%MOD+MOD)%MOD;
a[j][n+1]=(a[j][n+1]-ttf*a[i][n+1]%MOD+MOD)%MOD;
}
}
for(int i=n-1;i>=1;--i){
a[i][n+1]=(a[i][n+1]-a[i][i+1]*a[i+1][n+1]%MOD+MOD)%MOD;
}
}
int main(){
int T;scanf("%d",&T);
while(T--){
scanf("%lld%lld%lld%lld",&n,&p,&m,&k);
if(k==0){puts("-1");continue;}
if(m==0){
if(k==1){puts("-1");continue;}
ll ans=0;
while(p>0){
if(p<n)++p;
p-=k;++ans;
}printf("%lld\n",ans);
continue;
}
ll invm=fpr(m),invm1=fpr(m+1);
P[0]=fpr(m*invm1%MOD,k);
for(ll i=1;i<=min(n+1,k);++i)
P[i]=P[i-1]*invm%MOD*(k-i+1)%MOD*fpr(i)%MOD;
for(int i=1;i<n;++i){
for(int j=1;j<=i;++j)
a[i][j]=(P[i-j]*m%MOD+P[i-j+1])%MOD*invm1%MOD;
}
for(int i=1;i<n;++i)a[i][i+1]=P[0]*invm1%MOD;
for(int i=1;i<n;++i)a[i][i]=(a[i][i]-1+MOD)%MOD;
for(int i=1;i<=n;++i)a[i][n+1]=-1+MOD;
for(int i=1;i<=n;++i)a[n][i]=P[n-i];
a[n][n]=(a[n][n]-1+MOD)%MOD;
sp_gauss();//本题特殊的高消
printf("%lld\n",a[p][n+1]);
for(int i=0;i<=min(n+1,k);++i)P[i]=0;//没有清空导致错误。因为要保证大于范围的P为0,上次可能会有残留
}
return 0;
}
2.解决01状态上的问题
2.1 LG2962 [USACO09NOV]Lights G
给出一张 \(n\) 个点 \(m\) 条边的无向图,每个点的初始状态都为 \(0\)。
你可以操作任意一个点,操作结束后该点以及所有与该点相邻的点的状态都会改变,由 \(0\) 变成 \(1\) 或由 \(1\) 变成 \(0\)。
你需要求出最少的操作次数,使得在所有操作完成之后所有 \(n\) 个点的状态都是 \(1\)。
一个灯只有按或不按两种状态,每个灯的状态由其自己是否按和与它相邻的是否来异或得到,那么我们求出图的邻接矩阵 \(a_{i,j}\) 表示 \(i,j\) 之间有边,\(x_i\) 的值表示 \(i\) 号灯是否按。
那么就得到了异或高斯消元的式子:
但是这个矩阵并不一定是满秩的,这意味着高斯消元中会出现自由元,使得回带过程中并不能确定每个未知数的系数。
解决这个问题可以在回带的时候使用 dfs,如果遇到自由元就对他的状态进行枚举。具体见代码。
void guass(){//异或高斯消元成上三角矩阵
for(int i=1;i<=n;++i){
int loc=i;
while(loc<=n&&!a[loc][i])++loc;
if(loc>n)continue;
swap(a[loc],a[i]),swap(b[loc],b[i]);
for(int k=1;k<=n;++k){
if(i==k||!a[k][i])continue;
for(int j=i;j<=n;++j)
a[k][j]^=a[i][j];
b[k]^=b[i];
}
}
}
void dfs(int lef,int num){//枚举自由元,从下往上,(可以理解为回代的过程)
if(num>=ans)return;
if(lef==0){
ans=num;
return;
}
if(a[lef][lef]){//状态确定(非自由元)
bool cnt=b[lef];
for(int i=lef+1;i<=n;++i)
if(a[lef][i])cnt^=sta[i];//默认全关
dfs(lef-1,num+cnt);
}else{//枚举状态
sta[lef]=0;dfs(lef-1,num);
sta[lef]=1;dfs(lef-1,num+1);sta[lef]=0;
}
}
int main(){
n=read(),m=read();
for(int i=1;i<=m;++i){
int u=read(),v=read();
a[u][v]=a[v][u]=1;
}
for(int i=1;i<=n;++i)
b[i]=a[i][i]=1;
guass();dfs(n,0);
printf("%d\n",ans);
return 0;
}
2.2 LG3164 [CQOI2014]和谐矩阵
我们称一个由0和1组成的矩阵是和谐的,当且仅当每个元素都有偶数个相邻的1。一个元素相邻的元素包括它本身,及他上下左右的4个元素(如果存在)。给定矩阵的行数和列数,请计算并输出一个和谐的矩阵。注意:所有元素为0的矩阵是不允许的。\(1\leq n,m\leq 40\)。
就是求方程 \(a[x][y]\oplus a[x-1][y]\oplus a[x+1][y]\oplus a[x][y-1]\oplus a[x][y+1]=0\) 的解。
显然有 \(n\times m\) 个未知数,\(n\times m\) 个方程。
所以直接异或高斯消元的复杂度是 \(O((nm)^3)\)。可以使用 bitset 优化。
const int N=42,dx[]={-1,0,1,0,0},dy[]={0,1,0,-1,0};
int n,m,id[N][N];
bool ans[N*N];
bitset<N*N>a[N*N];//N*M 个点
void pp(int r){
for(int i=1;i<=r;++i){
for(int j=1;j<=r+1;++j)
printf("%d ",(int)a[i][j]);
putchar('\n');
}putchar('\n');
}
void gauss(int r){//01矩阵,bitset 优化
for(int i=1;i<=r;++i){
int loc=i;
while(loc<=r&&!a[loc][i])++loc;
if(a[loc][i])swap(a[loc],a[i]);
// else ans[i]=1;//自由元,随便赋值为1
for(int j=i+1;j<=r;++j)
if(a[j][i])a[j]^=a[i];
}
for(int i=r;i>=1;--i){
if(!a[i][i]){ans[i]=1;continue;}//在这里判断自由元逻辑自洽一些
for(int j=i+1;j<=r;++j)
ans[i]^=(ans[j]*a[i][j]);
}
}
int main(){
int n=read(),m=read();
for(int i=1;i<=n;++i){
for(int j=1;j<=m;++j)
id[i][j]=(i-1)*m+j;
}
for(int i=1;i<=n;++i){
for(int j=1;j<=m;++j){
int u=id[i][j];
for(int k=0;k<=4;++k){//包括原地不动
int mx=i+dx[k],my=j+dy[k];
if(mx<1||mx>n||my<1||my>m)
continue;
int v=id[mx][my];
a[u][v]=1;
}
}
}
gauss(n*m);
for(int i=1;i<=n*m;++i){
printf("%d ",ans[i]);
if(i%m==0)putchar('\n');
}
return 0;
}
2.3 LG2447 [SDOI2010] 外星千足虫
\(n\) 个数,\(m\) 条信息,每条信息给定一个长为 \(n\) 的二进制串,第 \(i\) 个字符为 \(0\) 表示编号为 \(i\) 的数没有放入,编号为 \(1\) 表示放入,接着是一个数 \(0/1\),为放入的数除以 \(2\) 的余数。
如果给定了某条信息后,可以确定各个数的奇偶性,就输出目前给定的信息数和各个数的奇偶性。如果所有信息都给定了而无法确定,输出无解。
假设第 \(i\) 个虫子的奇偶性是 \(x_i\),那么依据题意,可以给出方程组。
我们对这个方程组高斯消元即可。
由于需要判断前几个方程就足够,我们判断一下 \(loc\) 的值即可。然后这个问题也可以 bitset
优化。
此外这个问题用学术的话说就是求当前矩阵的秩。
const int N=2003;
bitset<N>a[N];
int n,m,ans;
char s[N];
int main(){
scanf("%d %d",&n,&m);
for(int i=1;i<=m;++i){
int x;
scanf("%s %d",s+1,&x);
for(int j=1;j<=n;++j)
a[i][j]=(s[j]=='1');
a[i][n+1]=x;
}
for(int i=1;i<=n;++i){
int loc=i;
while(loc<=m&&!a[loc][i])++loc;
if(loc==m+1)return puts("Cannot Determine"),0;
ans=max(ans,loc);
if(loc!=i)swap(a[loc],a[i]);
for(int j=1;j<=m;++j)
if((i!=j)&&a[j][i])a[j]^=a[i];
}
printf("%d\n",ans);
for(int i=1;i<=n;++i)
puts(a[i][n+1]?"?y7M#":"Earth");
return 0;
}
3. 矩阵树定理-行列式求值
3.1 LG4111 [HEOI2015]小 Z 的房间
你的房子可以看做是一个包含 \(n\times m\) 个格子的格状矩形,每个格子是一个房间或者是一个柱子。在一开始的时候,相邻的格子之间都有墙隔着。
你想要打通一些相邻房间的墙,使得所有房间能够互相到达。在此过程中,你不能把房子给打穿,或者打通柱子(以及柱子旁边的墙)。同时你希望任意两个房间之间都只有一条通路。现在,你希望统计一共有多少种可行的方案,答案对 \(10^9\) 取模。
第一行两个整数 \(n,m\)。
接下来 \(n\) 行,每行 \(m\) 个字符
.
或*
,其中.
代表房间,*
代表柱子。
将非柱子的点编号,加边的时候枚举 \((i,j)\) 左侧和上侧的点,遇到柱子不加边,可以保证不重复加边。
由于是无向图的生成树计数,所以对两条有向边邻接矩阵 \(+1\),对两点的度数矩阵 \(+1\)。最后用度数矩阵减去邻接矩阵,行列式求值即可。
#include <iostream>
#include <cstdio>
#include <cstring>
using namespace std;
inline int read(){
int x=0,f=1;char ch=getchar();
while(ch<'0'||'9'<ch){if(ch=='-')f=-1;ch=getchar();}
while('0'<=ch&&ch<='9'){x=x*10+(ch^48);ch=getchar();}
return x*f;
}
typedef long long ll;
const ll MOD=1000000000;
const int N=12;
int n,m,id[N][N],tot,a[N*N][N*N];
inline void add(int x,int y){
++a[x][x],++a[y][y],--a[x][y],--a[y][x];
}
char s[N][N];
ll gauss(int r){
ll f=1,tmp=1;
for(int i=1;i<=r;++i){
for(int k=i+1;k<=r;++k){
while(a[i][i]){
ll tim=a[k][i]/a[i][i];
for(int j=i;j<=r;++j)
a[k][j]=(a[k][j]-tim*a[i][j]%MOD+MOD)%MOD;
swap(a[k],a[i]);f=-f;
}swap(a[k],a[i]);f=-f;
}
}
for(int i=1;i<=r;++i)
tmp=tmp*a[i][i]%MOD;
tmp*=f;
return (tmp+MOD)%MOD;
}
int main(){
n=read(),m=read();
for(int i=1;i<=n;++i)
scanf("%s",s[i]+1);
for(int i=1;i<=n;++i){
for(int j=1;j<=m;++j)
if(s[i][j]=='.')id[i][j]=++tot;
}
for(int i=1;i<=n;++i){
for(int j=1;j<=m;++j){
if(!id[i][j])continue;
if(id[i][j-1])add(id[i][j-1],id[i][j]);
if(id[i-1][j])add(id[i-1][j],id[i][j]);
}
}
printf("%lld\n",gauss(tot-1));
return 0;
}
本文作者:BigSmall_En
本文链接:https://www.cnblogs.com/BigSmall-En/p/16572291.html
版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步