【矩阵树定理】【拉格朗日插值】JZOJ6461. 【GDOI2020模拟02.05】生成树

15 篇文章 0 订阅
2 篇文章 0 订阅

Description

传送门
给定一张 N 个点,M 条边的无向图,边有红、绿、蓝三种颜色,分别用 1,2,3 表示。
求这张图有多少生成树,满足绿色边数量不超过 x,蓝色边数量不超过 y,答案对10^9 + 7 取模。
1 ≤ N ≤ 40,1 ≤ M ≤ 10^5

矩阵树定理

  • 专门用来处理无向连通图生成树有关的计数问题。
  • 首先定义基尔霍夫(Kirchhoff)矩阵为 度 数 矩 阵 A − 邻 接 矩 阵 E 度数矩阵A-邻接矩阵E AE(都是n阶的)。 A [ i ] [ i ] A[i][i] A[i][i]为点i的度数。 E [ i ] [ j ] = 1 E[i][j]=1 E[i][j]=1表示 i , j i,j i,j有一条边。
  • 定理内容:该矩阵的任意一个代数余子式相等,且生成树的个数即为该矩阵的任意一个代数余子式。(代数余子式即挖掉某一行某一列后剩下的 n − 1 n-1 n1阶的矩阵的行列式)。
  • 运用高斯消元即可做到 n 3 n^3 n3的时间复杂度。

矩阵树定理的推广

  1. 广义上来说求得的应该是所有生成树的边权乘积之和。对于简单的无向图来说边权就是1,就可以套用上面的结论。如果 ( i , j ) (i,j) (i,j)间有很多条平行边,那么对应的邻接矩阵的位置也就不是1了,而是边的条数,相当于是套了一个权值。
  2. 由于求行列式是一系列相乘的计算的,所以我们矩阵的元素还可以是多项式(具体参照这道题目),而求行列式的过程涉及到了多项式乘法。应该注意到的是,度数也要根据组成的不同分成多项式。

Solution

  • 首先这道题目显然与矩阵树定理密切相关。
  • 而多种颜色的边我们可以用多项式表示,每一对 ( i , j ) (i,j) (i,j)的边权可以用(a+bx+cy)表示,常数项,x的系数,y的系数分别表示红绿蓝。那么求行列式的多项式乘法最终的答案也是一个多项式, x i y j x^iy^j xiyj的系数自然就是 i i i条绿, j j j条蓝边的方案数。
  • 感性理解。
  • 那么问题在于模数和时间复杂度,使得我们不能直接用多项式乘法,求逆去做高斯消元求行列式。
  • 注意到最后次数在n以内,所以我们拿出我们处理多项式问题的又一利器——拉格朗日插值。我们直接暴力 n 2 n^2 n2枚举n种i,n种j,做二维的拉格朗日插值即可。二维的拉格朗日插值与一维的大同小异。
    在这里插入图片描述
  • 至此我们需要 n 2 n^2 n2枚举 i , j i,j i,j,里面一个 n 3 n^3 n3的行列式,以及一个 n 2 n^2 n2的多项式暴力乘法.
  • O ( n 5 ) O(n^5) O(n5)
#include<cstdio>
#include<cmath>
#include<cstring>
#include<algorithm>
#define maxn 41
#define ll long long 
#define mo 1000000007
using namespace std;

int n,m,i,j,k,cx,cy,du[maxn],x,y,z,a[maxn][maxn][3];
ll inv[maxn],F[maxn][maxn],A[maxn][maxn],X[maxn],Y[maxn],Z[maxn];

void read(int &x){
	x=0; char ch=getchar();
	for(;ch<'0'||ch>'9';ch=getchar());
	for(;ch>='0'&&ch<='9';ch=getchar()) x=x*10+ch-'0';
}

ll ksm(ll x,ll y){
	ll s=1;
	for(;y;y/=2,x=x*x%mo) if (y&1)
		s=s*x%mo;
	return s;
}

ll Gauss(int x,int y){
	for(i=1;i<n;i++) for(j=1;j<n;j++) 
		A[i][j]=-a[i][j][0]-a[i][j][1]*x-a[i][j][2]*y;
	for(i=1;i<n;i++) {
		A[i][i]=0;
		for(j=1;j<=n;j++) 
			A[i][i]+=a[i][j][0]+a[i][j][1]*x+a[i][j][2]*y;
	}
	ll ans=1;
	for(i=1;i<n;i++) {
		if (!A[i][i]) {
			for(j=i+1;j<n;j++) if (A[j][i]){
				ans=-ans;
				for(k=1;k<n;k++) swap(A[i][k],A[j][k]);
				break;
			}
			if (!A[i][i]) return 0;
		}
		int inv0=ksm(A[i][i],mo-2); ans=ans*A[i][i]%mo;
		for(j=i;j<n;j++) A[i][j]=A[i][j]*inv0%mo;
		for(j=i+1;j<n;j++) for(k=n-1;k>=i;k--)
			(A[j][k]-=A[j][i]*A[i][k])%=mo;
	}
	for(i=1;i<n;i++) ans=ans*A[i][i]%mo;
	return ans;
}

int main(){
	read(n),read(m),read(cx),read(cy);
	for(i=1;i<=n;i++) inv[i]=ksm(i,mo-2);
	for(i=1;i<=m;i++) {
		read(x),read(y),read(z);
		a[x][y][z-1]++,a[y][x][z-1]++;
		du[x]++,du[y]++;
	}
	for(x=1;x<=n;x++) for(y=1;y<=n;y++){
		ll tmp=Gauss(x,y);
		memset(X,0,sizeof(X)),memset(Y,0,sizeof(Y));
		X[0]=Y[0]=1;
		for(k=0,i=1;i<=n;i++) if (i!=x){
			tmp=tmp*inv[abs(x-i)]%mo*((x>i)?1:-1);
			for(j=++k;j>=1;j--) X[j]=(-X[j]*i+X[j-1])%mo;
			X[0]=-X[0]*i%mo;
		}
		for(k=0,i=1;i<=n;i++) if (i!=y){
			tmp=tmp*inv[abs(y-i)]%mo*((y>i)?1:-1);
			for(j=++k;j>=1;j--) Y[j]=(-Y[j]*i+Y[j-1])%mo;
			Y[0]=-Y[0]*i%mo;
		}
		for(i=0;i<n;i++) for(j=0;j<n;j++)
			F[i][j]+=X[i]*Y[j]%mo*tmp%mo;
	}
	for(i=0;i<n;i++) for(j=0;j<n;j++)
		F[i][j]=(F[i][j]%mo+mo)%mo;
	ll ans=0;
	for(i=0;i<=cx;i++) for(j=0;j<=cy;j++)
		ans+=F[i][j]%mo;
	printf("%lld",(ans%mo+mo)%mo);
}

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值