[专题总结]矩阵树定理Matrix_Tree及题目&题解
专题做完了还是要说两句留下什么东西的。
矩阵树定理通俗点讲就是:
建立矩阵A[i][j]=-edge(i,j),(i!=j)。即矩阵这一项的系数是两点间直接相连的边数。
而A[i][i]=deg(i)。即对角线上都是这个点的度数。
得到这个矩阵后,随便删掉一行一列后进行高斯消元得到上三角矩阵,对角线上值的积就是生成树的个数。(就是行列式)
顺便提一下行列式的性质:
交换两行/列,行列式的值变为相反数。
一行的每一项减去另一行的若干倍,行列式不变。
一行的每一项都乘一个常数,行列式也乘这个常数。
到这里就够做题了。
T1:小Z的房间:
Descpiption:
板子。
1 #include<cstdio> 2 #include<algorithm> 3 #define mod 1000000000 4 int ord[11][11],n,m,tim,A[101][101];char s[11][11]; 5 void link(int a,int b){A[a][a]++;A[b][b]++;A[a][b]--;A[b][a]--;} 6 int Gauss(int ans=1){ 7 for(int i=1;i<=tim;++i)for(int j=i+1;j<=tim;++j)while(A[j][i]){ 8 int d=A[i][i]/A[j][i];for(int k=i;k<=tim;++k)A[i][k]=(A[i][k]-1ll*d*A[j][k]%mod+mod)%mod; 9 std::swap(A[i],A[j]);ans*=-1; 10 }for(int i=1;i<=tim;++i)ans=1ll*ans*A[i][i]%mod;return (ans+mod)%mod; 11 } 12 int main(){ 13 scanf("%d%d",&n,&m); 14 for(int i=1;i<=n;++i)scanf("%s",s[i]+1); 15 for(int i=1;i<=n;++i)for(int j=1;j<=m;++j)if(s[i][j]=='.')ord[i][j]=++tim; 16 for(int i=1;i<=n;++i)for(int j=1;j<=m;++j)if(s[i][j]=='.'){ 17 if(s[i-1][j]=='.')link(ord[i-1][j],ord[i][j]); 18 if(s[i][j-1]=='.')link(ord[i][j-1],ord[i][j]); 19 } 20 tim--;printf("%d\n",Gauss()); 21 }
T2:重建:
Description:
T 国有n(n<=50)个城市,用若干双向道路连接。一对城市之间至多存在一条道路。
在一次洪水之后,一些道路受损无法通行。虽然已经有人开始调查道路的损毁情况,但直到现在几乎没有消息传回。
幸运的是,此前 T 国政府调查过每条道路的强度,现在他们希望只利用这些信息估计灾情。
具体地,给定每条道路在洪水后仍能通行的概率,请计算仍能通行的道路恰有N-1条,且能联通所有城市的概率。spj:相对误差<1e-4
变元的了。
就是边带权,要求出所有生成树边权积的和。
那么统计度数和边权的时候都不再++,--,按照边权来就可以了。
但是这道题求的没有这么简单。
求的是恰好形成一棵树,不能有多边。即求$\sum\limits_{tree} \prod\limits_{i \in tree}w_i \prod\limits_{i \notin tree}(1-w_i)$
不在树里的不好处理,所以化一下得到$\sum\limits_{tree} \prod\limits_{i \in tree}\frac{w_i}{1-w_i} \prod\limits_i(1-w_i)$
然后就得到了新的边权。
要注意设个eps,当$1-w_i$很小时除法容易锅,如果它小于eps就把它强制置为eps。
1 #include<cstdio> 2 #include<algorithm> 3 #include<cmath> 4 using namespace std; 5 long double A[55][55];int n; 6 long double Gauss(){long double ans=1; 7 for(int i=1;i<n;++i){ 8 long double mx=0;int pt; 9 for(int j=i;j<n;++j)if(fabs(A[j][i])>mx)pt=j,mx=fabs(A[j][i]); 10 if(pt!=i)swap(A[pt],A[i]); 11 for(int j=i+1;j<n;++j){ 12 long double rate=A[j][i]/A[i][i]; 13 for(int k=i;k<=n;++k)A[j][k]-=rate*A[i][k]; 14 } 15 } 16 for(int i=1;i<n;++i)ans*=A[i][i];return ans; 17 } 18 int main(){ 19 scanf("%d",&n);long double pans=1; 20 for(int i=1;i<=n;++i)for(int j=1;j<=n;++j)scanf("%Lf",&A[i][j]); 21 for(int i=1;i<=n;++i)for(int j=i+1;j<=n;++j)pans*=1.0L-A[i][j]<1e-9?1e-9:1.0L-A[i][j]; 22 for(int i=1;i<=n;++i)for(int j=1;j<=n;++j)A[i][j]/=1.0L-A[i][j]<1e-9?1e-9:1.0L-A[i][j]; 23 for(int i=1;i<=n;++i)for(int j=1;j<=n;++j)if(i!=j)A[i][i]+=A[i][j]; 24 for(int i=1;i<=n;++i)for(int j=1;j<=n;++j)if(i!=j)A[i][j]=-A[i][j]; 25 printf("%.10Lf\n",Gauss()*pans); 26 }
T3:2467生成树:
Description:
有一种图形叫做五角形圈。一个五角形圈的中心有1个由n个顶点和n条边组成的圈。在中心的这个n(n<=100)边圈的每一条边同时也是某一个五角形的一条边,一共有n个不同的五角形。这些五角形只在五角形圈的中心的圈上有公共的顶点。如图0所示是一个4-五角形圈。 现在给定一个n五角形圈,你的任务就是求出n五角形圈的不同生成树的数目。还记得什么是图的生成树吗?一个图的生成树是保留原图的所有顶点以及顶点的数目减去一这么多条边,从而生成的一棵树。 注意:在给定的n五角形圈中所有顶点均视为不同的顶点。mod2007
板子。注意判断n=2
1 #include<cstdio> 2 #include<algorithm> 3 #define mod 2007 4 int A[404][404],n; 5 int Gauss(int ans=1){n--; 6 for(int i=1;i<=n;++i)for(int j=i+1;j<=n;++j)while(A[j][i]){ 7 int d=A[i][i]/A[j][i];for(int k=i;k<=n;++k)A[i][k]=(A[i][k]+mod-A[j][k]*d%mod)%mod; 8 std::swap(A[i],A[j]);ans*=-1; 9 }for(int i=1;i<=n;++i)ans=ans*A[i][i]%mod;return (ans+mod)%mod; 10 } 11 int main(){ 12 int t;scanf("%d",&t); 13 while(t--){ 14 scanf("%d",&n);n<<=2; 15 for(int i=1;i<n;++i)for(int j=1;j<n;++j)A[i][j]=0; 16 for(int i=1;i<n;++i)A[i][i]=i%4==1?4:2; 17 for(int i=1;i<n;++i)A[i][i+1]--,A[i+1][i]--; 18 A[1][n-3]--,A[n-3][1]--; 19 for(int i=1;i<n;i+=4)A[i][i+4]--,A[i+4][i]--; 20 printf("%d\n",Gauss()); 21 } 22 }
T4:黑暗前的幻想乡
Description:
四年一度的幻想乡大选开始了,最近幻想乡最大的问题是很多来历不明的妖怪涌入了幻想乡,扰乱了幻想乡昔日的秩序。但是幻想乡的建制派妖怪(人类)博丽灵梦和八云紫等人整日高谈所有妖怪平等,幻想乡多元化等等,对于幻想乡目前面临的种种大问题却给不出合理的解决方案。
风见幽香是幻想乡里少有的意识到了问题严重性的大妖怪。她这次勇敢地站了出来参加幻想乡大选,提出包括在幻想乡边境建墙(并让人类出钱),大力开展基础设施建设挽回失业率等一系列方案,成为了大选年出人意料的黑马并顺利地当上了幻想乡的大统领。
幽香上台以后,第一项措施就是要修建幻想乡的公路。幻想乡一共有n(n<=17)个城市,之前原来没有任何路。幽香向选民承诺要减税,所以她打算只修n-1条公路将这些城市连接起来。但是幻想乡有正n-1个建筑公司,每个建筑公司都想在修路地过程中获得一些好处。虽然这些建筑公司在选举前没有给幽香钱,幽香还是打算和他们搞好关系,因为她还指望他们帮她建墙。所以她打算让每个建筑公司都负责一条路来修。
每个建筑公司都告诉了幽香自己有能力负责修建的路是哪些城市之间的。所以幽香打算n-1条能够连接幻想乡所有城市的边,然后每条边都交给一个能够负责该边的建筑公司修建,并且每个建筑公司都恰好修建一条边。
幽香现在想要知道一共有多少种可能的方案呢?两个方案不同当且仅当它们要么修的边的集合不同,要么边的分配方式不同。mod1e9+7
数据范围很小,考虑枚举子集进行容斥。
奇加偶减,然后就是板子了。
1 #include<cstdio> 2 #include<algorithm> 3 #define mod 1000000007 4 int n,A[18][18],c[18][18][18],ans; 5 int Gauss(){int ans=1; 6 for(int i=1;i<n;++i)for(int j=i+1;j<n;++j)while(A[j][i]){ 7 int d=A[i][i]/A[j][i];for(int k=i;k<n;++k)A[i][k]=(A[i][k]-1ll*A[j][k]*d%mod+mod)%mod; 8 std::swap(A[i],A[j]);ans*=-1; 9 }for(int i=1;i<n;++i)ans=1ll*ans*A[i][i]%mod;return ans; 10 } 11 int main(){ 12 scanf("%d",&n); 13 for(int i=1;i<n;++i){ 14 int k,x,y;scanf("%d",&k); 15 while(k--)scanf("%d%d",&x,&y),c[i][x][x]++,c[i][y][y]++,c[i][x][y]--,c[i][y][x]--; 16 } 17 for(int st=1;st<1<<n-1;++st){ 18 for(int i=1;i<n;++i)for(int j=1;j<n;++j)A[i][j]=0; 19 for(int l=1;l<n;++l)if(st&1<<l-1) 20 for(int i=1;i<n;++i)for(int j=1;j<n;++j)A[i][j]+=c[l][i][j]; 21 int pt=1;for(int i=st;i;i^=i&-i)pt*=-1;if(n&1^1)pt*=-1;//printf("%d %d\n",st,pt); 22 ans=(0ll+ans+mod+pt*Gauss())%mod;//printf("%d\n",ans); 23 }printf("%d\n",ans); 24 }
T5:最小生成树计数
Description:
最后那个注意是真的要注意。。。
先来两发结论:
1。在不同的最小生成树中,每种权值的边用的数量是一定的。
2。在不同的最小生成树中,每种权值的边都加入完后,图的联通性是一定的。
(即如,你用并查集维护的话,如果每次合并都把小编号的当成根,那么得到并查集都完全一样)
仔细想一想,其实都不难证。
然后你只要状压每种边权的10条边用了哪些就好了。
也可以用matrix_tree做。但是我打的搜索。
1 #include<cstdio> 2 #include<algorithm> 3 #include<unordered_map> 4 #include<vector> 5 using namespace std; 6 unordered_map<int,int>M; 7 struct edge{ 8 int a,b,v; 9 friend bool operator<(edge A,edge B){ 10 return A.v<B.v; 11 } 12 }E[1005]; 13 vector<edge>v[1005]; 14 int n,m,f[105],cnt,uc[1005],p[1005][105],ans=1; 15 int find(int k){return f[k]==k?k:f[k]=find(f[k]);} 16 void merge(int a,int b){if(a<b)f[b]=a;else f[a]=b;} 17 int main(){ 18 scanf("%d%d",&n,&m); 19 for(int i=1;i<=n;++i)f[i]=i; 20 for(int i=1;i<=m;++i)scanf("%d%d%d",&E[i].a,&E[i].b,&E[i].v); 21 sort(E+1,E+1+m); 22 for(int i=1;i<=m;++i)if(M[E[i].v]!=cnt||cnt==0)M[E[i].v]=++cnt; 23 for(int i=1;i<=m;++i)E[i].v=M[E[i].v]; 24 for(int i=1;i<=m;++i)v[E[i].v].push_back(E[i]); 25 for(int i=1;i<=cnt;++i){ 26 for(int j=0;j<v[i].size();++j)if(find(v[i][j].a)!=find(v[i][j].b)) 27 merge(f[v[i][j].a],f[v[i][j].b]),uc[i]++; 28 for(int j=1;j<=n;++j)p[i][j]=find(j); 29 } 30 for(int i=1;i<=n;++i)if(find(i)!=1){puts("0");return 0;} 31 for(int i=1;i<=n;++i)p[0][i]=i; 32 for(int i=1;i<=cnt;++i){ 33 int pl=0;//printf("%d %d\n",i,pl); 34 for(int j=0;j<1<<v[i].size();++j){ 35 for(int l=1;l<=n;++l)f[l]=p[i-1][l]; 36 for(int l=0;l<v[i].size();++l)if(j&1<<l) 37 if(find(v[i][l].a)==find(v[i][l].b))goto B; 38 else merge(f[v[i][l].a],f[v[i][l].b]); 39 for(int l=1;l<=n;++l)if(find(l)!=p[i][l])goto B; 40 pl++;B:; 41 } 42 ans=ans*pl%31011;//printf("%d %d\n",i,pl); 43 }printf("%d\n",ans); 44 }
T6:轮状病毒
Description:
轮状病毒有很多变种,所有轮状病毒的变种都是从一个轮状基产生的。一个N轮状基由圆环上N个不同的基原子 和圆心处一个核原子构成的,2个原子之间的边表示这2个原子之间的信息通道。如下图所示
N轮状病毒的产生规律是在一个N轮状基中删去若干条边,使得各原子之间有唯一的信息通道,例如共有16个不 同的3轮状病毒,如下图所示
现给定n(N<=100),编程计算有多少个不同的n轮状病毒
感觉少了点什么。
对,你没看错,没有模数。需要高精,__int128卡不过的。
那么直接跑Gauss得T飞吧。
所以正确的姿势是,小点matrix_tree打表找规律。大点dp写高精。
给出dp式子$dp[i]=dp[i-1]\times 3 - dp[i-2] +2$
然后就打个高精就行。
1 #include<cstdio> 2 int n; 3 struct bigint{ 4 #define mod 100000000 5 int a[9]; 6 friend void operator+=(bigint &a,bigint b){ 7 for(int i=8;~i;--i)a.a[i]+=b.a[i]; 8 for(int i=0;i<=8;++i)if(a.a[i]>=mod)a.a[i]-=mod,a.a[i+1]++; 9 } 10 friend void operator-=(bigint &a,bigint b){ 11 for(int i=8;~i;--i)a.a[i]-=b.a[i]; 12 for(int i=0;i<=8;++i)if(a.a[i]<0)a.a[i]+=mod,a.a[i+1]--; 13 } 14 void print(int i=8){ 15 for(;~i;--i)if(a[i]){printf("%d",a[i]);break;} 16 for(--i;~i;--i)printf("%08d",a[i]);puts(""); 17 } 18 }ldp,nw,nxt; 19 int main(){ 20 nw.a[0]=1;int n;scanf("%d",&n);n--; 21 while(n--){ 22 nxt=nw;nxt+=nw;nxt+=nw;nxt-=ldp;nxt.a[0]+=2; 23 ldp=nw;nw=nxt; 24 }nw.print(); 25 }