[BZOJ4031][HEOI2015]小Z的房间(矩阵树定理+高斯消元)

=== ===

这里放传送门

=== ===

题解

没错这就是个裸题

矩阵树定理: 定义一个图的基尔霍夫矩阵为:

A[i][j]=d[i],1,i=jij
其中 d[i] 表示点 i 的度。 对于无向图来说,这个矩阵的任何一个n-1阶主子式的行列式的值就是这个图的不同生成树个数。
其中n-1阶主子式表示在矩阵中任意去掉标号相同的一行和一列以后剩下的子矩阵

但是这题模数实在是太 ! 恶 ! 心 ! 了!!!
ATP尝试了N多种方法包括什么最小公倍数乱搞。。
最后还是上网扒出了这种类似辗转相除的方法。。
原理在ATP写高斯消元的板子里有详细的解释[戳这里],ATP就懒得再打一遍了。。

代码

#include
#include
#include
#include
using namespace std;
const double eps=1e-8;
const long long Mod=1e9;
int n,m,mp[20][20],d[4][2]={{0,1},{1,0},{-1,0},{0,-1}},c[20][20],cnt;
long long ans,A[110][110],mak;
long long Abs(long long x){return (x<0)?-x:x;}
long long gcd(long long a,long long b){
    long long r=a%b;
    while (r!=0){a=b;b=r;r=a%b;}
    return b;
}
long long get_LCM(long long a,long long b){
    return (a/gcd(a,b)*b);
}
void addedge(int x,int y){
    int u,v,a,b;
    a=c[x][y];
    for (int i=0;i<4;i++){
        u=x+d[i][0];v=y+d[i][1];
        if (c[u][v]!=0){
            b=c[u][v];A[a][a]++;A[a][b]=-1;
        }
    }
}
/*void Gauss_Eli(int n){
    int num;
    for (int i=1;i<=n;i++){
        num=i;
        for (int j=i+1;j<=n;j++)
          if (Abs(A[j][i])>Abs(A[num][i])) num=j;
        for (int j=1;j<=n;j++) swap(A[i][j],A[num][j]);
        for (int j=i+1;j<=n;j++)
          if (A[j][i]!=0){
              long long lcm=get_LCM(A[i][i],A[j][i]),ta,tb;
              ta=lcm/Abs(A[j][i]);tb=lcm/Abs(A[i][i]);
              if (A[j][i]*A[i][i]<0) tb=-tb;
              for (int k=1;k<=n;k++)
                A[j][k]=(ta*A[j][k]-tb*A[i][k])%Mod;
          }
    }
}*/
void Gauss_Eli(int n){
    int num;
    for (int i=1;i<=n;i++){
        num=i;
        for (int j=i+1;j<=n;j++)
          if (Abs(A[j][i])>Abs(A[num][i])) num=j;
        if (num!=i) mak^=1;
        for (int j=1;j<=n;j++) swap(A[i][j],A[num][j]);
        for (int j=i+1;j<=n;j++)
          while (A[j][i]!=0){
              long long tmp=A[j][i]/A[i][i];
              for (int k=1;k<=n;k++)//做完一次操作以后A[j][i]相当于对A[i][i]取模了
                A[j][k]=(A[j][k]+Mod-tmp*A[i][k]%Mod)%Mod;
              if (A[j][i]==0) break;
              mak^=1;//注意要维护取反标记,不能直接取绝对值
              for (int k=1;k<=n;k++) swap(A[j][k],A[i][k]);
          }
    }
}
int main()
{
    scanf("%d%d",&n,&m);
    for (int i=1;i<=n;i++)
      for (int j=1;j<=m;j++){
          char s=getchar();
          while (s!='.'&&s!='*') s=getchar();
          if (s=='*') mp[i][j]=1;
          else c[i][j]=++cnt;
      }
    for (int i=1;i<=n;i++)
      for (int j=1;j<=m;j++)
        if (mp[i][j]!=1) addedge(i,j);
    Gauss_Eli(cnt-1);ans=1;
    for (int i=1;iif (mak==1) ans=Mod-ans;//因为是取模运算所以一定要注意正负
    ans=(ans+Mod)%Mod;
    printf("%I64d\n",ans);
    return 0;
}

你可能感兴趣的:(BZOJ,高斯消元消来消去)