必备知识:

  高斯消元,图论基本知识(好像就这。。。(雾))


这里是无向图部分,请不要走错场。。。

定义

  我们将邻接矩阵定义为矩阵\(A(u,v)\),我想邻接矩阵就不用再多说了;

  我们将每个点的度数矩阵定义为矩阵\(D(u,v)\),这里再加上数学表示;

  \(D(u,u)=u\)这个点的度数,\(D(u,v)=0(u!=v)\);

  我们将矩阵Laplace(或Kirchhoff)定义为\(L(u,v)=D(u,v)-A(u,v)\)

  我们将生成树的个数定义为 \(t\);

引入

  这里将讲述行列式,如果dalao已经学过,请直接跳过这个环节;

  这里引入的是\(N*N\)方阵行列式(因为邻接矩阵是方形),例如

  

  行列式的公式是

      

  PS:其中v是 \(l_1 , l_2 , l_3 ... l_n\)的逆序对个数;

  行列式有几个性质:

  •   行行交换,结果相反;
  •   行行叠加,结果不变;
  •   矩阵行伸长,结果等比例增加;

  PS:

  性质1的简单证明:

    由行列式的公式可知,行行交换,必然会出现逆序对的变化,变化为1,那么此时结果符号一定会改变;

  性质2的补充:

    可以让其他行乘上k叠加到这一行,结果不变;

  根据这些,我们就可以发现,高斯消元可以很好的利用这些性质,那么高斯消元后矩阵对角线的乘积即为结果;

  我从其他位置挖来了一个矩阵L的优化证明,说实话,我有点蒙

  

  我们根据这个性质可以少算一行一列,这应该也算优化吧(心虚~

应用

  这样思路就十分清晰了,这里的矩阵L行列式值即为生成树个数,那么我们就有了步骤:

  1. 首先构造矩阵L,根据公式\(L=D - A\),我们可以很方便地求出矩阵L,当然,在读入边时就可以直接操作,如边 \(u\) -> \(v\),我们不妨让\(f [ u ] [ v ]=f [ v ] [ u ] - -,f [ u ] [ u ]++,f [ v ] [ v ]++\).
  2. 然后将矩阵高斯消元,并求出对角线的乘积。
  3. 有时因为必须是整数,我们可以采用类似于辗转相除法的减去方法,下面将详细介绍。

模型

  模板题:小Z的房间

  思路清晰,只要将一个点和上下左右建边,构造矩阵L,用高斯消元求解;

  不过高斯消元一般求其小数形式,这里是不行的,因为是方案数(不可能是小数啊QWQ);

  这里就应用了类似于辗转相除的方法,回顾辗转相除,将两个数取\(mod\),然后交换位置,直到一个为0为止;

  高斯消元同样是将另一个数消为0,那么我们将函数改一下,如下(看注释):

ll Gauss(){ll ans=1;n=cent-1;//定理 1 的应用 ,cent为总点数 for(int i=1;i<=n;i++){for(int j=i+1;j<=n;j++){while(f[j][i]){//类辗转相除法 ,直到一个为0 int t=f[i][i]/f[j][i];//注意是int类型(向下取整),没减完,但是减后f[i][i]<f[j][i] for(int k=i;k<=n;k++)f[i][k]=(f[i][k]-f[j][k]*t%mod+mod)%mod;swap(f[i],f[j]);//交换位置辗转减 //交换位置是因为上面所说的 f[i][i]<f[j][i] ans=-ans;//交换位置要取反 }}ans=(ans*f[i][i]+mod)%mod;}return ans;
}

  应该解释的还算清楚,类似辗转相除的复杂度大约多了一个\(log n\),总复杂度\(O(n^{3}log n)\)我将整个代码放在这里,算一个模板吧:

#include<bits/stdc++.h>
#define ll long long
#define mod 1000000000
using namespace std;
int n,m,a[100][100],cent;
ll f[100][100];inline void add(int u,int v){f[u][u]++,f[u][v]--;
}//由于矩阵对称,所以加减一次就好。。。 ll Gauss(){ll ans=1;n=cent-1;//定理 1 的应用 ,cent为总点数 for(int i=1;i<=n;i++){for(int j=i+1;j<=n;j++){while(f[j][i]){//类辗转相除法 ,直到一个为0 int t=f[i][i]/f[j][i];//注意是int类型(向下取整),没减完,但是减后f[i][i]<f[j][i] for(int k=i;k<=n;k++)f[i][k]=(f[i][k]-f[j][k]*t%mod+mod)%mod;swap(f[i],f[j]);//交换位置辗转减 //交换位置是因为上面所说的 f[i][i]<f[j][i] ans=-ans;//交换位置要取反 }}ans=(ans*f[i][i]+mod)%mod;}return ans;
}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=='.') a[i][j]=++cent;//命名 }for(int i=1;i<=n;i++)for(int j=1;j<=m;j++){int now,rou;if(!(now=a[i][j])) continue;if(rou=a[i-1][j]) add(now,rou);if(rou=a[i][j-1]) add(now,rou);if(rou=a[i+1][j]) add(now,rou);if(rou=a[i][j+1]) add(now,rou);//构建L矩阵 }printf("%lld\n",(Gauss()+mod)%mod);//不能是负数。。。
}

深入

  懂了模板当然是不够的,我们应该见识一些技巧:

[SDOI2014]重建

  题目不再粘贴,我们直接叙述;

  这里同样是生成树个数,不过不同的是,这里具有边权值,不过这里并不碍事,我们将叙述有点权值将如何应对。

  根据高中概率的基本知识,两个没有交集的事件\(A,B\),概率分别为\(P(A),P(B)\),那么\(P(AB)=P(A)*P(B)\),这个应该都懂。

  那么每条边联通的概率为\(P(u,v)\),那么不连通的概率是\(1-P(u,v)\),这个应该很显然。

  那么一个生成树的概率\(P(G)= P(u,v)*(1 - P(x,y))\),其中u,v的边属于生成树\(G\),而\(x,y\)这条边不属于。

  那么,\(\sum P(G)\)即为总概率,我们将式子改造。

  \[\sum_{G} \prod_{(u,v)\in G,(x,y)\notin G} P(u,v)*(1-P(x,y))=\sum_G \prod_{(u,v)\in G,(x,y)\notin G} P(u,v)*(1-\frac{P(G)}{P(u,v)})=\sum_G (1 - P(G))\prod_{(u,v)\in G,(x,y)\notin G} \frac{P(u,v)}{1-P(u,v)}\]

  解释一下,由于属于树\(G\)的边和不属于树\(G\)的边互为补集,所以就可以利用这个性质,我直接表达这个式子

  \(sum=\sum(1-P(G)) \prod_{(u,v)\in G,(x,y)\notin G} \frac{P(u,v)}{1-P(u,v)}\)

  这样就很显然了,将\(\frac{P[u][v]}{1-P[u][v]}\)作为边权值,不过如何处理边权,这里给出步骤:

  1.我们将矩阵读入,重新定义边权;

  2.将边权当作度数加在对角线上,然后当作邻接矩阵中边的个数减去即可;

  3.高斯消元;

  介绍完毕,Code:

#include<bits/stdc++.h>
#define maxn 57
#define db double
using namespace std;
int n;
db f[maxn][maxn],ans=1.0;
const db eps=1e-8;db Gauss(){n--;db ol=1.0;for(int i=1;i<=n;i++){int sp=i;for(int j=i+1;j<=n;j++)if(fabs(f[j][i])>fabs(f[sp][i])) sp=j;if(i!=sp) swap(f[i],f[sp]),ans=-ans;for(int j=i+1;j<=n;j++){db t=f[j][i]/f[i][i];for(int k=i;k<=n;k++)f[j][k]-=f[i][k]*t;}ol*=f[i][i];}return ol;
}//这里是小数,所以操作就没有那么鬼畜了。。 //sum = sigma P(G) Π(P[u][v]/(1-P[u][v]));int main(){scanf("%d",&n);for(int i=1;i<=n;i++)for(int j=1;j<=n;j++)scanf("%lf",&f[i][j]);for(int i=1;i<=n;i++){for(int j=1;j<=n;j++){db t=max((1.0-f[i][j]),eps);//小心 0 哦 if(i>j) ans*=t;//累加,得出 1-P(G) f[i][j]/=t;//步骤1 }}for(int i=1;i<=n;i++)for(int j=1;j<=n;j++)if(i!=j){f[i][i]+=f[i][j]; f[i][j]=-f[i][j];}//步骤2 ,构图 printf("%.10lf",fabs(Gauss()*ans));
}

  [SHOI2016]黑暗前的幻想乡

  这里用到了容斥原理,二进制枚举等技巧,容斥我就不再叙述,自己不会可以yy一下(逃~

  直接上代码了(有良心注释)

#include<bits/stdc++.h>
#define maxn 19
#define ll long long
#define mod 1000000007
using namespace std;
int n,m;
vector<pair<int ,int > >a[maxn];
ll f[maxn][maxn],ans;ll Gauss(){int m=n-1;ll ol=1;for(int i=1;i<=m;i++){for(int j=i+1;j<=m;j++){while(f[j][i]){int t=f[i][i]/f[j][i];for(int k=i;k<=m;k++)f[i][k]=(f[i][k]-f[j][k]*t%mod+mod)%mod;swap(f[i],f[j]);ol=-ol;}}ol=ol*f[i][i]%mod;}return (ol+mod)%mod;
}//唉,方案数。。。 void add(int u,int v){f[u][u]++,f[v][v]++;f[u][v]--,f[v][u]--;
}int main(){scanf("%d",&n);for(int i=1,t,u,v;i<=n-1;i++){scanf("%d",&t);while(t--){scanf("%d%d",&u,&v);a[i].push_back(make_pair(u,v));//存图 }}int lim=1<<(n-1);for(int i=1;i<lim;i++){int cnt=0;memset(f,0,sizeof(f));//暴力建图 for(int k=0;k<n-1;k++){//二进制枚举 if((1<<k)&i){cnt++;for(int j=0;j<a[k+1].size();j++)add(a[k+1][j].first,a[k+1][j].second);}}if((n-cnt)&1) ans=(ans+Gauss())%mod;//容斥原理。。。其实就是奇数和偶数分别加减 。。。 else ans=(ans-Gauss()+mod)%mod;//防止负数。。。 }printf("%lld\n",ans);
}

总结

  其实还有一些内容,但是由于赶着复习,就没再说了。

  我们做的这几道题,无非是建图,统计答案,高斯消元时设下关卡,导致题目难度的跃升。

  但既然已经知道要考哪里,就往哪个地方想,就像专题训练一样,然后找出特点,从而在综合题中找到这个算法的影子;

  这个算法特点主要就是生成树的计数,所以应该很好看出来,记住特点和处理方法,培养数学思维才是做题目的;

转载于:https://www.cnblogs.com/waterflower/p/11508914.html

矩阵树定理(Kirchhoff || Laplace)初探——Part 1(无向图计数)相关推荐

  1. 学习小记-----行列式矩阵树定理Kirchhoff's theorem

    为什么我的标题要加上Kirchhoff's theorem呢,是因为之前我查这个定理是用这个英文在谷歌上查的,然后,,,,我看了20多分钟的英文维基百科,然后爬墙去做别的题目了QAQ 行列式 前置知识 ...

  2. 基尔霍夫(kirchhoff)矩阵树定理

    引入问题   给n个点m条边的图,求该图的最小生成树个数. 定理内容   我们对这个图构造两个矩阵,分别是这个图的连通矩阵和度数矩阵.连通矩阵S1S1S1的第iii行第jjj列上的数字表示原无向图中编 ...

  3. 【学习笔记】矩阵树定理(Matrix-Tree)

    整理的算法模板合集: ACM模板 点我看算法全家桶系列!!! 实际上是一个全新的精炼模板整合计划 目录 一.矩阵树定理 二.常用定理 三.例题 1. Luogu P6178 [模板]Matrix-Tr ...

  4. 矩阵树定理2020HDU多校第6场j-Expectation[位运算+期望]

    矩阵树定理 用于求解图上面生成树的个数,生成树的个数等于基尔霍夫矩阵的任何一个N-1阶主子式的行列式的绝对值 矩阵树模板 struct Matrix_Tree {ll a[N][N];Matrix_T ...

  5. Luogu P4336 [SHOI2016]黑暗前的幻想乡(容斥,矩阵树定理,子集反演)

    整理的算法模板合集: ACM模板 点我看算法全家桶系列!!! 实际上是一个全新的精炼模板整合计划 Luogu P4336 [SHOI2016]黑暗前的幻想乡(容斥,矩阵树定理) Problem n≤1 ...

  6. 图论数学:矩阵树定理

    运用矩阵树定理进行生成树计数 给定一个n个点m条边的无向图,问生成树有多少种可能 直接套用矩阵树定理计算即可 矩阵树定理的描述如下: 首先读入无向图的邻接矩阵,u-v G[u][v]++ G[v][u ...

  7. [CF917D]Stranger Trees[矩阵树定理+解线性方程组]

    题意 给你 \(n\) 个点的无向完全图,指定一棵树 \(S\),问有多少棵生成树和这棵树的公共边数量为 \(k\in[0,n-1]\) \(n\leq 100\) 分析 考虑矩阵树定理,把对应的树边 ...

  8. HDU多校6 - 6836 Expectation(矩阵树定理+高斯消元求行列式)

    题目链接:点击查看 题目大意:给出一张由 n 个点和 m 条边组成的无向图,对于任意一个生成树,其权值为 n - 1 条边的边权进行二进制的 and 运算,现在需要在图中任意选择一个生成树,问期望权值 ...

  9. Wannafly挑战赛23F-计数【原根,矩阵树定理,拉格朗日插值】

    正题 题目链接:https://ac.nowcoder.com/acm/contest/161/F 题目大意 给出nnn个点的一张图,求它的所有生成树中权值和为kkk的倍数的个数.输出答案对ppp取模 ...

最新文章

  1. 5年後、10年後の自分のイメージ
  2. C#读写xml文件最简单方法(操作配置文件)
  3. WinAPI: midiInReset - 重置输入
  4. java基本数据类型转为String类型的4种常见方法
  5. 【Qt教程】3.4 - Qt5 QPainter绘图事件、绘图功能
  6. 使用data attributes
  7. C++/CLI学习入门
  8. 【干货】java参考文献论文类
  9. 令人吃惊,这个短信平台在这些方面居然完胜阿里云
  10. 指挥系统核心服务器,应急指挥中心指挥调度系统解决方案(一)
  11. Elasticsearch常用查询命令
  12. CentOS 6.3 下 vsftpd 匿名用户访问配置
  13. 计算机硬件故障照片,计算机硬件故障的识别与处理
  14. 使用golang进行PDF处理,go-tika。就是这个是个warp的封装的版本。ledongthuc/pdf 的开源项目,速度快,解析中文也非常好。可以解析出简历PDF内容
  15. Linux下常见错误码
  16. Intelligent Designer
  17. 给体制内新人的忠告:这10个“潜规则”咬紧牙别吱声,只做不说
  18. 业余草通告CSDN博客用户zhang__ao非法转载文章的公告
  19. 软件质量保证与测试大作业,软件测试大作业.docx
  20. 使用whistle进行API代理

热门文章

  1. 美《外交政策》:世界5大黑帮现状
  2. PyTorch实战1——预测未来某地区租赁单车的使用情况
  3. Neo4j图数据库简介和底层原理
  4. SQL Server 2014安装笔记
  5. 微信第三方平台开发三(消息加解密)
  6. 李行亮秀上网神器:愿得一人心 路由要子母
  7. oracle DNS解析_DNS优选解决网络延迟
  8. SAP BOM反查(可追溯值至顶级)
  9. unity保存图片到手机相册,安卓
  10. ​2022年第112期(Radiology): 肿瘤免疫治疗影像学的新概念和发展变化