Description
傳送門
給定一張 N 個點,M 條邊的無向圖,邊有紅、綠、藍三種顏色,分別用 1,2,3 表示。
求這張圖有多少生成樹,滿足綠色邊數量不超過 x,藍色邊數量不超過 y,答案對10^9 + 7 取模。
1 ≤ N ≤ 40,1 ≤ M ≤ 10^5
矩陣樹定理
- 專門用來處理無向連通圖生成樹有關的計數問題。
- 首先定義基爾霍夫(Kirchhoff)矩陣爲(都是n階的)。爲點i的度數。表示有一條邊。
- 定理內容:該矩陣的任意一個代數餘子式相等,且生成樹的個數即爲該矩陣的任意一個代數餘子式。(代數餘子式即挖掉某一行某一列後剩下的階的矩陣的行列式)。
- 運用高斯消元即可做到的時間複雜度。
矩陣樹定理的推廣
- 廣義上來說求得的應該是所有生成樹的邊權乘積之和。對於簡單的無向圖來說邊權就是1,就可以套用上面的結論。如果間有很多條平行邊,那麼對應的鄰接矩陣的位置也就不是1了,而是邊的條數,相當於是套了一個權值。
- 由於求行列式是一系列相乘的計算的,所以我們矩陣的元素還可以是多項式(具體參照這道題目),而求行列式的過程涉及到了多項式乘法。應該注意到的是,度數也要根據組成的不同分成多項式。
Solution
- 首先這道題目顯然與矩陣樹定理密切相關。
- 而多種顏色的邊我們可以用多項式表示,每一對的邊權可以用(a+bx+cy)表示,常數項,x的係數,y的係數分別表示紅綠藍。那麼求行列式的多項式乘法最終的答案也是一個多項式,的係數自然就是條綠,條藍邊的方案數。
- 感性理解。
- 那麼問題在於模數和時間複雜度,使得我們不能直接用多項式乘法,求逆去做高斯消元求行列式。
- 注意到最後次數在n以內,所以我們拿出我們處理多項式問題的又一利器——拉格朗日插值。我們直接暴力枚舉n種i,n種j,做二維的拉格朗日插值即可。二維的拉格朗日插值與一維的大同小異。
- 至此我們需要枚舉,裏面一個的行列式,以及一個的多項式暴力乘法.
#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);
}