【矩陣樹定理】【拉格朗日插值】JZOJ6461. 【GDOI2020模擬02.05】生成樹

Description

傳送門
給定一張 N 個點,M 條邊的無向圖,邊有紅、綠、藍三種顏色,分別用 1,2,3 表示。
求這張圖有多少生成樹,滿足綠色邊數量不超過 x,藍色邊數量不超過 y,答案對10^9 + 7 取模。
1 ≤ N ≤ 40,1 ≤ M ≤ 10^5

矩陣樹定理

  • 專門用來處理無向連通圖生成樹有關的計數問題。
  • 首先定義基爾霍夫(Kirchhoff)矩陣爲AE度數矩陣A-鄰接矩陣E(都是n階的)。A[i][i]A[i][i]爲點i的度數。E[i][j]=1E[i][j]=1表示i,ji,j有一條邊。
  • 定理內容:該矩陣的任意一個代數餘子式相等,且生成樹的個數即爲該矩陣的任意一個代數餘子式。(代數餘子式即挖掉某一行某一列後剩下的n1n-1階的矩陣的行列式)。
  • 運用高斯消元即可做到n3n^3的時間複雜度。

矩陣樹定理的推廣

  1. 廣義上來說求得的應該是所有生成樹的邊權乘積之和。對於簡單的無向圖來說邊權就是1,就可以套用上面的結論。如果(i,j)(i,j)間有很多條平行邊,那麼對應的鄰接矩陣的位置也就不是1了,而是邊的條數,相當於是套了一個權值。
  2. 由於求行列式是一系列相乘的計算的,所以我們矩陣的元素還可以是多項式(具體參照這道題目),而求行列式的過程涉及到了多項式乘法。應該注意到的是,度數也要根據組成的不同分成多項式。

Solution

  • 首先這道題目顯然與矩陣樹定理密切相關。
  • 而多種顏色的邊我們可以用多項式表示,每一對(i,j)(i,j)的邊權可以用(a+bx+cy)表示,常數項,x的係數,y的係數分別表示紅綠藍。那麼求行列式的多項式乘法最終的答案也是一個多項式,xiyjx^iy^j的係數自然就是ii條綠,jj條藍邊的方案數。
  • 感性理解。
  • 那麼問題在於模數和時間複雜度,使得我們不能直接用多項式乘法,求逆去做高斯消元求行列式。
  • 注意到最後次數在n以內,所以我們拿出我們處理多項式問題的又一利器——拉格朗日插值。我們直接暴力n2n^2枚舉n種i,n種j,做二維的拉格朗日插值即可。二維的拉格朗日插值與一維的大同小異。
    在這裏插入圖片描述
  • 至此我們需要n2n^2枚舉i,ji,j,裏面一個n3n^3的行列式,以及一個n2n^2的多項式暴力乘法.
  • O(n5)O(n^5)
#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);
}

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章