对于一个图 G G G,定义其度数矩阵为 D ( G ) D(G) D(G). D ( G ) D(G) D(G)是一个 n ∗ n n*n n∗n大小的对角线矩阵.
对角线上元素 d ( i , i ) d_{(i,i)} d(i,i)为顶点 i i i的度数。(非对角线上的元素 d ( i , j ) d_{(i,j)} d(i,j)为0)
对于一个图 G G G,定义其临接矩阵为 A ( G ) A(G) A(G).
a ( i , j ) a_{(i,j)} a(i,j)为 v i v_i vi和 v j v_j vj之间邻接的边数(注意可以有重边,即大于1)
K i r c h o f f 为 一 个 n ∗ n 的 矩 阵 Kirchoff为一个n*n的矩阵 Kirchoff为一个n∗n的矩阵: D ( G ) − A ( G ) D(G)-A(G) D(G)−A(G)
即对于 ∀ i , j , K i , j = D i , j − A i , j ∀i,j,K_{i,j}=D_{i,j}-A_{i,j} ∀i,j,Ki,j=Di,j−Ai,j
定理:对于一个图 G G G,其生成树个数为该Kirchoff矩阵行列式的值。
证明略过,会用即可。
用高斯消元法即可求得行列式的值。
模板如下(借用了洛谷某位大佬的):
void add(int u,int v) {
--a[u][v],--a[v][u],++a[u][u],++a[v][v];
}
ll Gauss(int n) {
ll ans=1;
for(int i=1;i<=n;++i) {
for(int k=i+1;k<=n;++k) {
while(a[k][i]) {
ll d=a[i][i]/a[k][i];
for(int j=i;j<=n;++j) a[i][j]=(a[i][j]-1LL*d*a[k][j]%mod+mod)%mod;
std::swap(a[i],a[k]),ans=-ans;
}
}
ans=1LL*ans*a[i][i]%mod,ans=(ans+mod)%mod;
}
return ans;
}
注意调用Gauss函数时传入的是n-1.
例题传送门:
第一题
附上这位大佬的代码(我比较懒就没敲了):
大佬传送门
#include <cstdio>
#include <algorithm>
const int N=15,M=105;
const int mod=1e9;
char s[N][N];
int n,m,a[M][M],id[N][N];
void add(int u,int v) {
--a[u][v],--a[v][u],++a[u][u],++a[v][v];
}
int Gauss(int n) {
int ans=1;
for(int i=1;i<=n;++i) {
for(int k=i+1;k<=n;++k) {
while(a[k][i]) {
int d=a[i][i]/a[k][i];
for(int j=i;j<=n;++j) a[i][j]=(a[i][j]-1LL*d*a[k][j]%mod+mod)%mod;
std::swap(a[i],a[k]),ans=-ans;
}
}
ans=1LL*ans*a[i][i]%mod,ans=(ans+mod)%mod;
}
return ans;
}
int main() {
scanf("%d%d",&n,&m);
for(int i=1;i<=n;++i) scanf("%s",s[i]+1);
int idx=0;
for(int i=1;i<=n;++i) for(int j=1;j<=m;++j) if(s[i][j]=='.') id[i][j]=++idx;
for(int i=1;i<=n;++i) for(int j=1;j<=m;++j) if(s[i][j]=='.') {
if(id[i-1][j]) add(id[i][j],id[i-1][j]);
if(id[i][j-1]) add(id[i][j],id[i][j-1]);
}
printf("%d\n",Gauss(idx-1));
return 0;
}
第二题
对于二进制每一位拆位计算贡献即可。
#include <bits/stdc++.h>
using namespace std;
#define MAXN 300050
#define ll long long
const int N=105,M=105;
const ll mod=998244353;
int n,m;
ll a[N][N];
ll qpow(ll x,ll y)
{
ll res=1;
while(y)
{
if(y&1)res=res*x%mod;
x=x*x%mod;
y>>=1;
}
return res;
}
ll inv(ll x)
{
return qpow(x,mod-2);
}
void add(int u,int v) {
--a[u][v],--a[v][u],++a[u][u],++a[v][v];
}
ll Gauss(int n) {
ll ans=1;
for(int i=1;i<=n;++i) {
for(int k=i+1;k<=n;++k) {
while(a[k][i]) {
ll d=a[i][i]/a[k][i];
for(int j=i;j<=n;++j) a[i][j]=(a[i][j]-1LL*d*a[k][j]%mod+mod)%mod;
std::swap(a[i],a[k]),ans=-ans;
}
}
ans=1LL*ans*a[i][i]%mod,ans=(ans+mod)%mod;
}
return ans;
}
int d[N][N][32];
int main() {
int t;
//freopen("E://tt.txt","r",stdin);
cin>>t;
while(t--)
{
memset(d,0, sizeof(d));
memset(a,0, sizeof(a));
scanf("%d%d",&n,&m);
for(int i=1;i<=m;i++)
{
int u,v,w;
scanf("%d%d%d",&u,&v,&w);
for(int j=0;j<30;j++)
{
if(w&(1ll<<j))
d[u][v][j]++,d[v][u][j]++;
}
add(u,v);
}
ll all=Gauss(n-1);
ll ans=0;
for(int j=0;j<30;j++)
{
memset(a,0, sizeof(a));
for(int i=1;i<=n;i++)
for(int k=i+1;k<=n;k++)
{
if(d[i][k][j]) {
for (int c = 0; c < d[i][k][j]; c++)
add(i, k);
}
}
ans+=(1ll<<j)*Gauss(n-1)%mod;
ans%=mod;
}
ans=ans*inv(all)%mod;
cout<<ans<<endl;
}
}
第三题
容斥即可,建议自己手写关于这个组合数的式子证明一下结论的正确性:
a n s = ∑ i = 1 n ( − 1 ) i − 1 F ( n + 1 − i ) ans=\sum_{i=1}^{n}(-1)^{i-1}F(n+1-i) ans=∑i=1n(−1)i−1F(n+1−i)
F ( i ) F(i) F(i)表示最多 i i i个公司参与修建的生成树个数.
#include <bits/stdc++.h>
using namespace std;
const int N=18,M=18;
const long long mod=1e9+7;
#define ll long long
ll n,a[M][M],id[N][N];
int m[N];
int v[N][N*N][2];
inline void add(int u,int v) {
--a[u][v],--a[v][u],++a[u][u],++a[v][v];
}
int D[30];
inline int Gauss(int n) {
int ans=1;
for(register int i=1;i<=n;++i) {
for(register int k=i+1;k<=n;++k) {
while(a[k][i]) {
int d=a[i][i]/a[k][i];
for(register int j=i;j<=n;++j) a[i][j]=(a[i][j]-1LL*d*a[k][j]%mod+mod)%mod;
std::swap(a[i],a[k]),ans=-ans;
}
}
ans=1LL*ans*a[i][i]%mod,ans=(ans+mod)%mod;
}
return ans;
}
int main() {
scanf("%lld",&n);n--;
for(int i=0;i<=n;i++)D[i]=1<<i;
for(int i=1;i<=n;++i) {
scanf("%lld",&m[i]);
for(int j=0;j<m[i];j++)
{
int u,w;scanf("%d%d",&u,&w);
v[i][j][0]=u;
v[i][j][1]=w;
}
}
int all=1<<(n);
long long ans=0;
for(register int i=1;i<all;++i)
{
memset(a,0, sizeof(a));
int cnt=0;
for(register int j=1;j<=n;++j)
{
if(i&D[j-1])
{
cnt++;
for(register int k=0;k<m[j];++k)
{
add(v[j][k][0],v[j][k][1]);
}
}
}
if((n-cnt)&1)
ans=(ans-Gauss(n)+mod)%mod;
else
ans=(ans+Gauss(n))%mod;
}
cout<<ans<<endl;
}