首页 > 其他分享 >矩阵树定理、BEST 定理

矩阵树定理、BEST 定理

时间:2023-02-11 20:22:44浏览次数:47  
标签:prod 15 int 定理 矩阵 include BEST

说句闲话。今天翻到一篇博客上来给放了个公式:

\[\sum_{i=0}^n\binom{2i}i\binom{2n-2i}{2i}=4^i \]

看起来就很不对劲。然后爆算了一波确实是错的。敬请注意。

然后不知道为什么要管 Vizing 定理叫维京定理。感觉不如味精定理。感觉不如胃镜定理。

是不是应该反思一下树上背包都能写挂的问题。或者反思一下是不是太过于信任大样例的问题。

感觉我再不打 rated 就要橙了。

矩阵树定理

oi-wiki 上边的矩阵树定理内容毫不客气地说真的跟个 jb 一样。并不是说讲的不对,只是我觉得大多数人都不想看这一大车定义和公式,而且还是线性代数的。

这玩意拿来计数一张图的生成树个数。直接上结论,证明不会。

以下默认没有自环。

原版

计数无向图生成树个数。

定义无向图的邻接矩阵 \(A\),\(A_{i,j}\) 为 \(1\) 当且仅当 \(i,j\) 间有边。定义度数矩阵 \(D\) ,其中 \(D_{i,i}\) 为节点 \(i\) 度数。定义无向图的基尔霍夫矩阵为 \(K=D-A\)。

那么随便找个 \(k\) 给矩阵 \(K\) 去掉第 \(k\) 行第 \(k\) 列得到 \(K'\),答案就是 \(\det(K')\)。

一道例题:[HEOI2015]小 Z 的房间

建图套板子即可。

#include <cstdio>
#include <cstring>
#include <algorithm>
#include <iostream>
#include <cmath>
using namespace std;
const int mod=1000000000;
int n,m,cnt;
int a[610][610],id[15][15];
char s[15][15];
int gauss(int n){
    int ans=1,w=1;
	for(int i=1;i<=n;i++){
		for(int j=i+1;j<=n;j++){
			while(a[i][i]){
				int rate=a[j][i]/a[i][i];
				for(int k=i;k<=n;k++){
					a[j][k]=(a[j][k]-1ll*rate*a[i][k]%mod+mod)%mod;
				}
				for(int k=1;k<=n;k++)swap(a[i][k],a[j][k]);
				w=-w;
			}
			for(int k=1;k<=n;k++)swap(a[i][k],a[j][k]);
			w=-w;
		}
	}
	for(int i=1;i<=n;i++)ans=1ll*ans*a[i][i]%mod;
	ans=(1ll*ans*w%mod+mod)%mod;
	return ans;
}
void add(int x,int y){
    a[x][x]++;a[y][y]++;a[x][y]--;a[y][x]--;
}
signed main(){
	scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++){
        scanf("%s",s[i]+1);
        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++){
            if(s[i][j]=='.'&&s[i+1][j]=='.')add(id[i][j],id[i+1][j]);
            if(s[i][j]=='.'&&s[i][j+1]=='.')add(id[i][j],id[i][j+1]);
        }
    }
    printf("%d\n",gauss(cnt-1));
	return 0;
}

边带权

对于边带权的情况,只需要把邻接矩阵的 \(A_{i,j}\) 改为 \(i,j\) 间所有边的边权和,度数矩阵的 \(D_{i,i}\) 改为从 \(i\) 连出去的所有边的边权和,得到的答案就是所有生成树边权乘积的和。

另一道例题:[SDOI2014]重建

一个生成树 \(T\) 的概率显然是

\[\prod_{e\in T}p_e\prod_{e\notin T}(1-p_e) \]

稍微转化一下:

\[\begin{aligned} &\prod_{e\in T}p_e\prod_{e\notin T}(1-p_e)\\ =&\prod_{e}(1-p_e)\prod_{e\in T}\frac{p_e}{1-p_e} \end{aligned} \]

那么把每条边的权值设为 \(\dfrac {p_e}{1-p_e}\) 就行了。如果是 \(1\) 的话减个 \(eps\) 就行。

#include <cstdio>
#include <cstring>
#include <algorithm>
#include <iostream>
#include <cmath>
using namespace std;
const double eps=1e-8;
int n,m,cnt;
double a[60][60],p[60][60];
double gauss(int n){
    double ans=1;
    int w=1;
	for(int i=1;i<=n;i++){
		for(int j=i+1;j<=n;j++){
			while(fabs(a[i][i])>eps){
				double rate=a[j][i]/a[i][i];
				for(int k=i;k<=n;k++){
					a[j][k]=a[j][k]-rate*a[i][k];
				}
				for(int k=1;k<=n;k++)swap(a[i][k],a[j][k]);
				w=-w;
			}
			for(int k=1;k<=n;k++)swap(a[i][k],a[j][k]);
			w=-w;
		}
	}
	for(int i=1;i<=n;i++)ans=ans*a[i][i];
	ans=ans*w;
	return ans;
}
void add(int x,int y){
    a[x][x]++;a[y][y]++;a[x][y]--;a[y][x]--;
}
signed main(){
	scanf("%d",&n);
    double ret=1;
    for(int i=1;i<=n;i++){
        for(int j=1;j<=n;j++){
            scanf("%lf",&p[i][j]);
            if(i==j)continue;
            if(p[i][j]>1-eps)p[i][j]-=eps;
            if(i<j)ret*=1-p[i][j];
            p[i][j]/=1-p[i][j];
        }
    }
    for(int i=1;i<=n;i++){
        for(int j=1;j<=n;j++){
            a[i][i]+=p[i][j];a[i][j]-=p[i][j];
        }
    }
    printf("%.4lf\n",gauss(n-1)*ret);
	return 0;
}

有向图

对于有向图,我们更改一下 \(A\) 和 \(D\)。此处的 \(A\) 变成了有向图的邻接矩阵,而 \(D\) 有两种不同情况:

  1. \(D_{i,i}\) 为点 \(i\) 入度,此时求的是外向树(从根向外指)。
  2. \(D_{i,i}\) 为点 \(i\) 出度,此时求的是内向树(从外向根指)。

然后关于去掉一行一列的问题,有向图的问题中去掉哪一行那一列就是以哪个点为根。

当然也可以带权。以下为模板代码。

#include <cstdio>
#include <cstring>
#include <algorithm>
#include <iostream>
#include <cmath>
using namespace std;
const int mod=1000000007;
int n,m,cnt;
int a[610][610],id[15][15];
char s[15][15];
int gauss(int n){
    int ans=1,w=1;
	for(int i=1;i<=n;i++){
		for(int j=i+1;j<=n;j++){
			while(a[i][i]){
				int rate=a[j][i]/a[i][i];
				for(int k=i;k<=n;k++){
					a[j][k]=(a[j][k]-1ll*rate*a[i][k]%mod+mod)%mod;
				}
				for(int k=1;k<=n;k++)swap(a[i][k],a[j][k]);
				w=-w;
			}
			for(int k=1;k<=n;k++)swap(a[i][k],a[j][k]);
			w=-w;
		}
	}
	for(int i=1;i<=n;i++)ans=1ll*ans*a[i][i]%mod;
	ans=(1ll*ans*w%mod+mod)%mod;
	return ans;
}
void add(int x,int y,int w){
    (a[x][x]+=w)%=mod;(a[y][y]+=w)%=mod;a[x][y]=(a[x][y]-w+mod)%mod;a[y][x]=(a[y][x]-w+mod)%mod;
}
signed main(){
    int t;scanf("%d%d%d",&n,&m,&t);
    for(int i=1;i<=m;i++){
        int u,v,w;scanf("%d%d%d",&u,&v,&w);
        if(t==0)add(u,v,w);
        else{
            a[v-1][v-1]=(a[v-1][v-1]+w)%mod;;a[u-1][v-1]=(a[u-1][v-1]-w+mod)%mod;
        }
    }
    printf("%d\n",gauss(n-1));
	return 0;
}

BEST 定理

内容:有向图欧拉回路数目:

\[ans=T\prod_{i}(out_i-1)! \]

其中 \(T\) 是内向生成树个数(容易证明每个节点为根的答案都一样),\(out_i\) 是 \(i\) 的出度。

注意这个没有算上从根开始的路径数目。所以如果从根开始走的路径不同的话需要乘上根节点的出度。

一道例题:「BZOJ3659」WHICH DREAMED IT

裸的 BEST 定理,注意答案乘上根节点出度。

然而这题特判非常之多。需要判是否是欧拉图、图是否联通、是否没有边等等情况。我甚至一度怀疑是不是我行列式求值噶了。

#include <cstdio>
#include <cstring>
#include <algorithm>
#include <iostream>
#include <cmath>
using namespace std;
const int mod=1000003;
int n,m,cnt;
int a[110][110],ind[110],out[110],jc[200010],fa[110];
int find(int x){
    return fa[x]==x?fa[x]:fa[x]=find(fa[x]);
}
int gauss(int n){
    int ans=1,w=1;
	for(int i=1;i<=n;i++){
        if(!out[i])continue;
		for(int j=i+1;j<=n;j++){
            if(!out[j])continue;
			while(a[i][i]){
				int rate=a[j][i]/a[i][i];
				for(int k=i;k<=n;k++){
					a[j][k]=(a[j][k]-1ll*rate*a[i][k]%mod+mod)%mod;
				}
				for(int k=1;k<=n;k++)swap(a[i][k],a[j][k]);
				w=-w;
			}
			for(int k=1;k<=n;k++)swap(a[i][k],a[j][k]);
			w=-w;
		}
	}
	for(int i=1;i<=n;i++)if(out[i])ans=1ll*ans*a[i][i]%mod;
	ans=(1ll*ans*w%mod+mod)%mod;
	return ans;
}
void clear(){
    for(int i=1;i<=n;i++)ind[i]=out[i]=0,fa[i]=i;
    for(int i=1;i<=n;i++){
        for(int j=1;j<=n;j++)a[i][j]=0;
    }
}
void solve(){
    scanf("%d",&n);clear();
    for(int i=1;i<=n;i++){
        scanf("%d",&m);
        for(int j=1;j<=m;j++){
            int x;scanf("%d",&x);
            ind[x]++;out[i]++;
            a[i][i]++;a[i][x]--;
            fa[find(x)]=find(i);
        }
    }
    for(int i=1;i<=n;i++){
        for(int j=1;j<=n;j++){
            a[i][j]=(a[i][j]+mod)%mod;
        }
    }
    if(n==1){
        printf("%d\n",jc[out[1]]);return;
    }
    for(int i=1;i<=n;i++){
        if(ind[i]!=out[i]||(find(i)!=find(1)&&ind[i])){
            puts("0");return;
        }
    }
    bool jud=false;
    for(int i=1;i<=n;i++)jud=(out[i]);
    if(!jud){
        puts("1");return;
    }
    int ans=gauss(n-1);
    for(int i=1;i<=n;i++)if(out[i])ans=1ll*ans*jc[out[i]-1]%mod;
    ans=1ll*ans*out[1]%mod;
    printf("%d\n",ans);
}
int main(){
    jc[0]=1;
    for(int i=1;i<=200000;i++)jc[i]=1ll*jc[i-1]*i%mod;
    int tim;scanf("%d",&tim);
    while(tim--)solve();
    return 0;
}

标签:prod,15,int,定理,矩阵,include,BEST
From: https://www.cnblogs.com/gtm1514/p/17112485.html

相关文章

  • 「解题报告」[省选联考 2021 A 卷] 矩阵游戏
    啥都不会了!啥都不会了!啥都不会了!啥都不会了!啥都不会了!啥都不会了!啥都不会了!啥都不会了!啥都不会了!啥都不会了!啥都不会了!啥都不会了!啥都不会了!啥都不会了!啥都不会了!啥都不会......
  • 重塑矩阵(力扣简单题)
    题目:在MATLAB中,有一个非常有用的函数reshape,它可以将一个mxn矩阵重塑为另一个大小不同(rxc)的新矩阵,但保留其原始数据。给你一个由二维数组mat表示的mxn矩......
  • Burnside引理和Pólya定理
    不想写很多冗杂的群论定义,所以本博客不是用来入门的。概要对于一个作用在集合\(X\)上的有限群\(G\),对于每个\(g\inG\)令\(X^g\)表示\(X\)在\(g\)作用下的不......
  • 【数组】——螺旋矩阵
    【数组】——螺旋矩阵模拟顺时针画矩阵的过程:1.填充上行从左到右2.填充右列从上到下3.填充下行从右到左4.填充左列从下到上由外向内一圈一圈这么画下去。每一条边都......
  • [蓝桥杯 2019 省 A] 组合数问题-Lucas定理、数位dp
    洛谷的题目链接:https://www.luogu.com.cn/problem/P8688Lucas定理,把\(k|binom{i}{j}\)转换成在k进制下存在某个数位i比j小,再转换成反面计算每一位i都比j大,然后就是经典......
  • 用行列式求4阶逆矩阵
    矩阵M的逆矩阵等于MT的C*1/detMC=Cofactory第一步转置 第二步就是求每个位置的代数余子式的值(举个例子M的a11就变为C11的值 ) 当前位置i+j奇偶决定正负4阶的Cij......
  • Time Series Analysis (Best MSE Predictor & Best Linear Predictor)
    TimeSeriesAnalysisBestMSE(MeanSquareError)Predictor对于所有可能的预测函数\(f(X_{n})\),找到一个使\(\mathbb{E}\big[\big(X_{n}-f(X_{n})\big)^{2}\big]......
  • 在VSCode中的markdown里插入混淆矩阵HTML源码
    最近在看论文的时候习惯用markdown记录笔记,就有了如题的需求。由于原生的markdown不能合并表格的单元格(或者我不知道,OS:真菜),但是markdown支持HTML,直接写一段代码扔进去就......
  • Numpy中数组和矩阵操作的数学函数
    Numpy是一个强大的Python计算库。它提供了广泛的数学函数,可以对数组和矩阵执行各种操作。本文中将整理一些基本和常用的数学操作。基本数学运算:Numpy提供了许多基本......
  • C语言填空:余弦定理 已知三边求面积
    //已知三角形两边及夹角(角度制),求第三边及面积#include<stdio.h>【1】【2】main(){floata,b,c,alfa,s;【3】scanf("%f%f%f",&a,&b,&alfa);【4】c=sqrt(a*......