【bzoj4894】天赋 矩阵树定理
题目描述
小明有许多潜在的天赋,他希望学习这些天赋来变得更强。正如许多游戏中一样,小明也有n种潜在的天赋,但有一些天赋必须是要有前置天赋才能够学习得到的。也就是说,有一些天赋必须是要在学习了另一个天赋的条件下才能学习的。比如,要想学会"开炮",必须先学会"开枪"。一项天赋可能有多个前置天赋,但只需习得其中一个就可以学习这一项天赋。上帝不想为难小明,于是小明天生就已经习得了1号天赋-----"打架"。于是小明想知道学习完这n种天赋的方案数,答案对1,000,000,007取模。(两种方案不同指的是存在某种天赋的前置天赋不同)
输入
第一行一个整数n。
接下来是一个n*n的01矩阵,第i行第j列为1表示习得天赋j的一个前置天赋为i。
数据保证第一列和主对角线全为0。
n<=300
输出
第一行一个整数,问题所求的方案数。
样例输入
8
01111111
00101001
01010111
01001111
01110101
01110011
01111100
01110110
样例输出
72373
题解
矩阵树定理
读明白题以后发现求的就是外向树形图的个数,于是使用矩阵树定理解决。
与求生成树个数不同的是,外向树形图用的矩阵是 入度矩阵-邻接矩阵 ,并且删去的一行一列不能随便选择,必须是根所在的那一行那一列。
然后高斯消元求一下行列式的值即可。
#include <cstdio> #include <cstring> #include <algorithm> #define N 310 #define mod 1000000007 using namespace std; typedef long long ll; ll a[N][N]; char str[N]; inline ll pow(ll x , ll y) { ll ans = 1; while(y) { if(y & 1) ans = ans * x % mod; x = x * x % mod , y >>= 1; } return ans; } int main() { int n , i , j , k , d = 0; ll t , ans = 1; scanf("%d" , &n); for(i = 0 ; i < n ; i ++ ) { scanf("%s" , str); for(j = 0 ; j < n ; j ++ ) if(str[j] == '1') a[j][j] ++ , a[i][j] -- ; } for(i = 1 ; i < n ; i ++ ) { for(j = i ; j < n ; j ++ ) if(a[j][i]) break; if(j >= n) continue; if(j != i) for(d ^= 1 , k = i ; k < n ; k ++ ) swap(a[i][k] , a[j][k]); ans = ans * a[i][i] % mod; for(t = pow(a[i][i] , mod - 2) , j = i ; j < n ; j ++ ) a[i][j] = a[i][j] * t % mod; for(j = i + 1 ; j < n ; j ++ ) for(t = a[j][i] , k = i ; k < n ; k ++ ) a[j][k] = (a[j][k] - a[i][k] * t % mod + mod) % mod; } for(i = 1 ; i < n ; i ++ ) ans = ans * a[i][i] % mod; if(d) ans = (mod - ans) % mod; printf("%lld\n" , ans); return 0; }