BZOJ 4031: [HEOI2015]小Z的房间(生成树计数)

本文介绍了一个基于MatrixTree定理的生成树计数问题解决方法,通过使用基尔霍夫矩阵并结合辗转相除法处理mod问题,实现了高效计算。文章提供了详细的算法流程及代码实现。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题目

生成树计数(这么总结题意真的好嘛。。。)

分析

Matrix Tree定理裸题
而Matrix Tree定理就是用来生成树计数的
行列式的求法:
对于一个矩阵进行高斯消元
最终案为(-1)^S*(主对角线之积),其中S位行与行之间交换的次数

Matrix-Tree定理:用于计算生成树计数问题
理论基于基尔霍夫(Kirchhoff)矩阵,但是这里不去证明了:
介绍两个矩阵:
度数矩阵D:c[i][i]表示i的度数,其他的都是0
邻接矩阵A:就是常规意义上的0/1邻接矩阵,c[i][i]=0
那么基尔霍夫矩阵就是D-A
生成树的答案就是他的n-1阶主子式的行列式的绝对值,所谓n-1阶主子式,就是去掉第r行r列之后的新矩阵
我相信正常人都会让r=n

辗转相除法处理mod问题
我们最终目的就是为了消元,但是在有mod又没有逆元的时候,我们就要换一种方式
按照辗转相除的思想,我们最终可以让一项为0
具体操作步骤就是t=a[i][j]/a[i+1][j],然后所有元素都减去下一排*t
然后交换两排元素(保证当前排最大),继续重复操作
注意这种方法用在行列式上,辗转相除带来的交换也算是交换。
由于mod没有负数,所以必须维护符号位,如果是负数,那么答案就是mod-ans

代码

#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
const int maxn=12,mod=1e9;
LL A[maxn*maxn][maxn*maxn];
int n,m,cnt;
char s[maxn][maxn];
int id[maxn][maxn];
int dx[]={1,-1,0,0};
int dy[]={0,0,1,-1};
void Init()
{
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++)
        scanf("%s",s[i]+1);
    int ni,nj;
    for(int i=1;i<=n;i++)
        for(int j=1;j<=m;j++)
            if(s[i][j]=='.')id[i][j]=++cnt;
    for(int i=1;i<=n;i++)
    {
        for(int j=1;j<=m;j++)
        {
            for(int k=0;k<4;k++)
            {
                ni=i+dx[k],nj=j+dy[k];
                if(!id[ni][nj])continue;
                A[id[i][j]][id[i][j]]++;
                A[id[ni][nj]][id[i][j]]--;
            }
        }
    }
}
int Gauss(int n,int m)
{
    for(int i=1;i<=n+1;i++)
        for(int j=1;j<=m+1;j++)
            if(A[i][j]<0)A[i][j]+=mod;
    int i=1,j=1,k,r,c;
    bool mark=0;

    while(i<=m && j<=n)
    {
        for(r=i,k=i+1;k<=m;k++)
            if(abs(A[k][j])>abs(A[r][j]))r=k;
        if(abs(A[r][j])>0)
        {
            if(i!=r)
            {
                mark^=1;
                for(c=j;c<=n;c++)swap(A[r][c],A[i][c]);
            }
            for(k=i+1;k<=m;k++)
            {
                while(A[k][j])
                {
                    int tmp=A[k][j]/A[i][j];
                    for(c=j;c<=n;c++)A[k][c]=(A[k][c]-A[i][c]*tmp%mod+mod)%mod;
                    if(!A[k][j])break;
                    mark^=1;
                    for(c=j;c<=n;c++)swap(A[k][c],A[i][c]);
                }
            }
            i++;
        }
        j++;
    }
    LL ret=1;
    for(int i=1;i<=n;i++)
        ret=(ret*A[i][i])%mod;
    if(mark)
        return (mod-ret)%mod;
    return ret;
}
int main()
{
    //freopen("in.txt","r",stdin);
    Init();
    printf("%d\n",Gauss(cnt-1,cnt-1));
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值