【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;
}

 

 

posted @ 2017-09-08 10:07  GXZlegend  阅读(312)  评论(0编辑  收藏  举报