用户工具

站点工具


2020-2021:teams:legal_string:jxm2001:矩阵树定理

差别

这里会显示出您选择的修订版和当前版本之间的差别。

到此差别页面的链接

两侧同时换到之前的修订记录 前一修订版
后一修订版
前一修订版
2020-2021:teams:legal_string:jxm2001:矩阵树定理 [2020/07/23 22:22]
jxm2001
2020-2021:teams:legal_string:jxm2001:矩阵树定理 [2020/07/27 22:59] (当前版本)
jxm2001 ↷ 页面2020-2021:teams:legal_string:jxm_矩阵树定理被移动并更名为2020-2021:teams:legal_string:jxm2001:矩阵树定理
行 1: 行 1:
-====== 矩树定理 ======+====== 矩树定理 ======
  
 ===== 算法简介 ===== ===== 算法简介 =====
  
-一种生成树的计定理,时间复杂度 $O\left(n^3\right)$。+一种生成树的计定理,时间复杂度 $O\left(n^3\right)$。
  
 ===== 算法实现 ===== ===== 算法实现 =====
行 41: 行 41:
 <hidden 查看代码>​ <hidden 查看代码>​
 <code cpp> <code cpp>
-#include <​iostream>​ 
-#include <​cstdio>​ 
-#include <​cstdlib>​ 
-#include <​algorithm>​ 
-#include <​string>​ 
-#include <​sstream>​ 
-#include <​cstring>​ 
-#include <​cctype>​ 
-#include <​cmath>​ 
-#include <​vector>​ 
-#include <set> 
-#include <map> 
-#include <​stack>​ 
-#include <​queue>​ 
-#include <​ctime>​ 
-#include <​cassert>​ 
-#define _for(i,a,b) for(int i=(a);​i<​(b);​++i) 
-#define _rep(i,a,b) for(int i=(a);​i<​=(b);​++i) 
-#define mem(a,b) memset(a,​b,​sizeof(a)) 
-using namespace std; 
-typedef long long LL; 
-inline int read_int(){ 
- int t=0;bool sign=false;​char c=getchar();​ 
- while(!isdigit(c)){sign|=c=='​-';​c=getchar();​} 
- while(isdigit(c)){t=(t<<​1)+(t<<​3)+(c&​15);​c=getchar();​} 
- return sign?-t:t; 
-} 
-inline LL read_LL(){ 
- LL t=0;bool sign=false;​char c=getchar();​ 
- while(!isdigit(c)){sign|=c=='​-';​c=getchar();​} 
- while(isdigit(c)){t=(t<<​1)+(t<<​3)+(c&​15);​c=getchar();​} 
- return sign?-t:t; 
-} 
-inline char get_char(){ 
- char c=getchar();​ 
- while(c=='​ '​||c=='​\n'​||c=='​\r'​)c=getchar();​ 
- return c; 
-} 
-inline void write(LL x){ 
- register char c[21],​len=0;​ 
- if(!x)return putchar('​0'​),​void();​ 
- if(x<​0)x=-x,​putchar('​-'​);​ 
- while(x)c[++len]=x%10,​x/​=10;​ 
- while(len)putchar(c[len--]+48);​ 
-} 
-inline void space(LL x){write(x),​putchar('​ ');} 
-inline void enter(LL x){write(x),​putchar('​\n'​);​} 
 const int MAX_size=305,​mod=1e9+7;​ const int MAX_size=305,​mod=1e9+7;​
 struct Matrix{ struct Matrix{
行 169: 行 122:
 <hidden 查看代码>​ <hidden 查看代码>​
 <code cpp> <code cpp>
-#include <​iostream>​ 
-#include <​cstdio>​ 
-#include <​cstdlib>​ 
-#include <​algorithm>​ 
-#include <​string>​ 
-#include <​sstream>​ 
-#include <​cstring>​ 
-#include <​cctype>​ 
-#include <​cmath>​ 
-#include <​vector>​ 
-#include <set> 
-#include <map> 
-#include <​stack>​ 
-#include <​queue>​ 
-#include <​ctime>​ 
-#include <​cassert>​ 
-#define _for(i,a,b) for(int i=(a);​i<​(b);​++i) 
-#define _rep(i,a,b) for(int i=(a);​i<​=(b);​++i) 
-#define mem(a,b) memset(a,​b,​sizeof(a)) 
-using namespace std; 
-typedef long long LL; 
-inline int read_int(){ 
- int t=0;bool sign=false;​char c=getchar();​ 
- while(!isdigit(c)){sign|=c=='​-';​c=getchar();​} 
- while(isdigit(c)){t=(t<<​1)+(t<<​3)+(c&​15);​c=getchar();​} 
- return sign?-t:t; 
-} 
-inline LL read_LL(){ 
- LL t=0;bool sign=false;​char c=getchar();​ 
- while(!isdigit(c)){sign|=c=='​-';​c=getchar();​} 
- while(isdigit(c)){t=(t<<​1)+(t<<​3)+(c&​15);​c=getchar();​} 
- return sign?-t:t; 
-} 
-inline char get_char(){ 
- char c=getchar();​ 
- while(c=='​ '​||c=='​\n'​||c=='​\r'​)c=getchar();​ 
- return c; 
-} 
-inline void write(LL x){ 
- register char c[21],​len=0;​ 
- if(!x)return putchar('​0'​),​void();​ 
- if(x<​0)x=-x,​putchar('​-'​);​ 
- while(x)c[++len]=x%10,​x/​=10;​ 
- while(len)putchar(c[len--]+48);​ 
-} 
-inline void space(LL x){write(x),​putchar('​ ');} 
-inline void enter(LL x){write(x),​putchar('​\n'​);​} 
 const int MAX_size=55;​ const int MAX_size=55;​
 const double eps=1e-8; const double eps=1e-8;
行 266: 行 172:
  
 现在给出了一个简单无向加权图,求这个图中有多少个不同的最小生成树,结果对 $31011$ 的模。 现在给出了一个简单无向加权图,求这个图中有多少个不同的最小生成树,结果对 $31011$ 的模。
 +
 +=== 性质 ===
 +
 +  - 如果 $A,B$ 都是 $G$ 的最小生成树,则将 $A,B$ 中所有边权从小到大排序,将得到相同结果。
 +  - 如果 $A,B$ 都是 $G$ 的最小生成树,则删去 $A,B$ 中权值超过 $w$ 的边后,$A,​B$ 图连通性完全相同。( $w$ 取值任意)
 +
 +== 证明 ==
 +
 +对性质一,将 $A,B$ 中所有边按边权从小到大排序,得 $a_1,​a_2,​\cdots a_n$ 和 $b_1,​b_2,​\cdots b_n$。
 +
 +考虑 $A,B$ 第一次出现边不相同的位置,有 $a_i\neq b_i$,不妨设 $w(a_i)\ge w(b_i)$。
 +
 +情况一:存在 $a_j=b_i$,则有 $j\gt i,​w(b_i)=w(a_j)\ge w(a_i)\ge w(b_i)$。
 +
 +所以有 $w(a_i)=w(b_i)=w(a_j)$,交换 $a_i,​a_j$,序列 $\{w(a)\}$ 不改变。
 +
 +情况二:不存在 $a_j=b_i$,考虑把 $b_i$ 加入 $A$,得到一个环,由于 $A$ 是最小生成树,故环上所有边权不超过 $w(b_i)$。
 +
 +同时环上一定有某条边不属于 $B$,否则 $B$ 上存在环,不妨记这条边为 $a_j$。
 +
 +则有 $j\gt i,w(b_i)\ge w(a_j)\ge w(a_i)\ge w(b_i)$,所以有 $w(a_i)=w(b_i)=w(a_j)$。
 +
 +考虑把边 $a_j$ 换成 $b_i$,然后交换 $a_i,a_j$ 的在序列中位置,序列 $\{w(a)\}$ 不改变。
 +
 +最后总有序列 $\{a\},​\{b\}$ 完全相同,于是有初始的 $\{w(a)\}$ 与 $\{w(b)\}$ 完全相同,证毕。
 +
 +对性质二,只能给出不太严谨的证明。考虑 $\text{Kruskal}$ 算法过程。
 +
 +由于 $\text{Kruskal}$ 中权值相同的边排序任意,说明按任意顺序考虑权值相同的边,都不会影响后续结果。
 +
 +所有相同权值的边选取结束后,图的连通性必然相同,证毕。
  
 === 题解 === === 题解 ===
 +
 +从小到大考虑每个不同的边权。考虑某个边权 $w$,记所有边权相同的边为边集 $E_w$。
 +
 +根据性质二,所有边权小于 $w$ 的边选择完成后图的连通性是确定的。于是对选择完成后的图进行缩点,然后计算从边集 $E_w$ 中选边的合法方案。
 +
 +计算合法方案时发现即使选择完边集 $E_w$ 中的边后也不能保证图是树,所以如果直接使用矩阵树定理将返回 $0$。
 +
 +于是考虑添加一些虚边保证合法选取 $E_w$ 中的边后图形一定是树。
 +
 +由于合法选取 $E_w$ 中的边后图的连通性也是确定的,所以可以考虑在合法选取 $E_w$ 中的边后图中加入一些虚边使得图恰好连通。
 +
 +发现将任意某个最小生成树中所有权值大于 $w$ 作为虚边加入图中恰好能满足条件,然后使用矩阵树定理即可计算方案数。
 +
 +最终答案即为所有不同的边权的选择方案的乘积。
 +
 +但题目给定的 $31011$ 并不是素数,所以计算行列式的消元过程中不能直接计算逆元。
 +
 +考虑在消元过程中模拟辗转相除法。但辗转相除法只对两个数操作,而消元需要对两行所有数操作。计算行列式复杂度变为 $O(n^3\log v)$。
 +
 +考虑在辗转相除过程中维护系数,即维护 $i'​=ai+bj,​j'​=ci+dj$,可以将计算行列式复杂度降为 $O(n^3)$。
 +
 +最后发现每次消元中 $n=$ 连通块个数 $=$ 最小生成树 $T$ 中边权为 $w$ 的边的个数 $\text{cnt}_w$,于是总时间复杂度为
 +
 +\begin{equation}O\left(\sum_{w\in T}\text{cnt}_w^3\right)=O(|T|^3)=O(n^3)\end{equation}
 +
 +<hidden 查看代码>​
 +<code cpp>
 +const int MAX_size=105,​MAXN=105,​MAXM=1e3+5,​mod=31011;​
 +struct Matrix{
 + int ele[MAX_size][MAX_size];​
 +};
 +int sp_gcd(int a,int b,​pair<​int,​int>​ &​x,​pair<​int,​int>​ &y){
 + int sign=1;
 + x.first=1,​x.second=0,​y.first=0,​y.second=1;​
 + while(b){
 + x.first=(x.first-a/​b*y.first)%mod;​
 + x.second=(x.second-a/​b*y.second)%mod;​
 + a%=b;
 + swap(x,​y);​
 + swap(a,​b);​
 + sign=-sign;​
 + }
 + return sign;
 +}
 +int det(Matrix a,int n,int mod){
 + int ans=1;
 + _rep(i,​2,​n){
 + int pos=i;
 + _rep(j,​i,​n)if(a.ele[j][i]){pos=j;​break;​}
 + if(!a.ele[pos][i])return 0;
 + if(pos!=i){_rep(j,​i,​n) swap(a.ele[i][j],​a.ele[pos][j]);​ans=mod-ans;​}
 + pair<​int,​int>​ x,y;
 + int t1,t2;
 + _rep(j,​i+1,​n){
 + ans*=sp_gcd(a.ele[i][i],​a.ele[j][i],​x,​y);​
 + _rep(k,​i,​n){
 + t1=a.ele[i][k],​t2=a.ele[j][k];​
 + a.ele[i][k]=(t1*x.first+t2*x.second)%mod;​
 + a.ele[j][k]=(t1*y.first+t2*y.second)%mod;​
 + }
 + }
 + ans=ans*a.ele[i][i]%mod;​
 + }
 + return (ans+mod)%mod;​
 +}
 +struct Edge{
 + int u,v,w;
 + bool operator < (const Edge &​b)const{
 + return w<b.w;
 + }
 +}edge[MAXM];​
 +bool vis[MAXM];
 +int p[MAXN],​block_id[MAXN],​block_cnt;​
 +int Find(int x){return x==p[x]?​x:​p[x]=Find(p[x]);​}
 +int main()
 +{
 + int n=read_int(),​m=read_int(),​u,​v,​w;​
 + Matrix X;
 + _rep(i,​1,​m)
 + edge[i].u=read_int(),​edge[i].v=read_int(),​edge[i].w=read_int();​
 + sort(edge+1,​edge+1+m);​
 + _rep(i,​1,​n)
 + p[i]=i;
 + block_cnt=n;​
 + for(int i=1;​i<​=m&&​block_cnt>​1;​i++){
 + int x=Find(edge[i].u),​y=Find(edge[i].v);​
 + if(x!=y)
 + p[x]=y,​vis[i]=true,​block_cnt--;​
 + }
 + if(block_cnt>​1){
 + puts("​0"​);​
 + return 0;
 + }
 + int ans=1;
 + for(int pos1=1,​pos2=1;​pos1<​=m;​pos1=pos2){
 + _rep(i,​1,​n)p[i]=i,​block_id[i]=0;​
 + _rep(i,​1,​m){
 + if(vis[i]&&​edge[i].w!=edge[pos1].w)
 + p[Find(edge[i].u)]=Find(edge[i].v);​
 + }
 + block_cnt=0;​
 + _rep(i,​1,​n){
 + if(!block_id[Find(i)])block_id[Find(i)]=++block_cnt;​
 + block_id[i]=block_id[Find(i)];​
 + }
 + _rep(i,​1,​block_cnt)_rep(j,​1,​block_cnt)X.ele[i][j]=0;​
 + while(edge[pos2].w==edge[pos1].w){
 + u=block_id[edge[pos2].u],​v=block_id[edge[pos2].v];​
 + X.ele[u][v]--;​X.ele[v][u]--;​
 + X.ele[u][u]++;​X.ele[v][v]++;​
 + pos2++;
 + }
 + ans=ans*det(X,​block_cnt,​mod)%mod;​
 + }
 + enter(ans);​
 + return 0;
 +}
 +</​code>​
 +</​hidden>​
2020-2021/teams/legal_string/jxm2001/矩阵树定理.1595514152.txt.gz · 最后更改: 2020/07/23 22:22 由 jxm2001