【bzoj4031】[HEOI2015]小Z的房间 && 【bzoj4894】天赋 (矩阵树定理)
来两道矩阵树模板:
T1:【bzoj4031】[HEOI2015]小Z的房间
Description
你突然有了一个大房子,房子里面有一些房间。事实上,你的房子可以看做是一个包含n*m个格子的格状矩形,每个格子是一个房间或者是一个柱子。在一开始的时候,相邻的格子之间都有墙隔着。
你想要打通一些相邻房间的墙,使得所有房间能够互相到达。在此过程中,你不能把房子给打穿,或者打通柱子(以及柱子旁边的墙)。同时,你不希望在房子中有小偷的时候会很难抓,所以你希望任意两个房间之间都只有一条通路。现在,你希望统计一共有多少种可行的方案。
你想要打通一些相邻房间的墙,使得所有房间能够互相到达。在此过程中,你不能把房子给打穿,或者打通柱子(以及柱子旁边的墙)。同时,你不希望在房子中有小偷的时候会很难抓,所以你希望任意两个房间之间都只有一条通路。现在,你希望统计一共有多少种可行的方案。
Input
第一行两个数分别表示n和m。
接下来n行,每行m个字符,每个字符都会是’.’或者’*’,其中’.’代表房间,’*’代表柱子。
接下来n行,每行m个字符,每个字符都会是’.’或者’*’,其中’.’代表房间,’*’代表柱子。
Output
一行一个整数,表示合法的方案数 Mod 10^9
题解:
这是一道典型的矩阵树裸题……
不过,这取模 10^9 不是质数,不能普通的高斯消元,要使用辗转相除法。
1 int f=1; 2 for(int i=1;i<cnt;i++){ 3 for(int j=i+1;j<=cnt;j++){ 4 while(a[j][i]){ 5 int t=a[j][i]/a[i][i]; 6 for(int k=i;k<=cnt;k++) 7 a[j][k]=(a[j][k]-1LL*a[i][k]*t%mod+mod)%mod; 8 if(a[j][i]==0)break; 9 for(int k=i;k<=cnt;k++)swap(a[i][k],a[j][k]); 10 f=-f; 11 } 12 } 13 }
这里涉及到交换,貌似统计答案要变为相反数……
COMPLETE CODE:
1 #include<cstdio> 2 #include<algorithm> 3 using namespace std; 4 5 #define mod 1000000000 6 int n,m,cnt,ans=1,a[100][100],id[10][10]; 7 char s[10]; 8 9 int qpow(int x,int y){ 10 int ans=1; 11 while(y){ 12 if(y&1)ans=1LL*ans*x%mod; 13 y>>=1,x=1LL*x*x%mod; 14 } 15 return ans; 16 } 17 18 int main(){ 19 scanf("%d%d",&n,&m); 20 for(int i=1;i<=n;i++){ 21 scanf("%s",s+1); 22 for(int j=1;j<=m;j++){ 23 if(s[j]=='*')continue; 24 id[i][j]=++cnt; 25 if(id[i-1][j]){ 26 a[id[i-1][j]][id[i][j]]--; 27 a[id[i][j]][id[i-1][j]]--; 28 a[id[i][j]][id[i][j]]++; 29 a[id[i-1][j]][id[i-1][j]]++; 30 }if(id[i][j-1]){ 31 a[id[i][j-1]][id[i][j]]--; 32 a[id[i][j]][id[i][j-1]]--; 33 a[id[i][j]][id[i][j]]++; 34 a[id[i][j-1]][id[i][j-1]]++; 35 } 36 } 37 } 38 cnt--; 39 for(int i=1;i<=cnt;i++) 40 for(int j=1;j<=cnt;j++) 41 if(a[i][j]<0)a[i][j]+=mod; 42 int f=1; 43 for(int i=1;i<cnt;i++){ 44 for(int j=i+1;j<=cnt;j++){ 45 while(a[j][i]){ 46 int t=a[j][i]/a[i][i]; 47 for(int k=i;k<=cnt;k++) 48 a[j][k]=(a[j][k]-1LL*a[i][k]*t%mod+mod)%mod; 49 if(a[j][i]==0)break; 50 for(int k=i;k<=cnt;k++)swap(a[i][k],a[j][k]); 51 f=-f; 52 } 53 } 54 } 55 for(int i=1;i<=cnt;i++) 56 ans=1LL*ans*a[i][i]%mod; 57 printf("%d",~f?ans:(mod-ans)%mod); 58 }
T2:【bzoj4894】天赋
Description
小明有许多潜在的天赋,他希望学习这些天赋来变得更强。正如许多游戏中一样,小明也有n种潜在的天赋,但有一些天赋必须是要有前置天赋才能够学习得到的。也就是说,有一些天赋必须是要在学习了另一个天赋的条件下才能学习的。比如,要想学会"开炮",必须先学会"开枪"。一项天赋可能有多个前置天赋,但只需习得其中一个就可以学习这一项天赋。上帝不想为难小明,于是小明天生就已经习得了1号天赋-----"打架"。于是小明想知道学习完这n种天赋的方案数,答案对1,000,000,007取模。(两种方案不同指的是存在某种天赋的前置天赋不同)
Input
第一行一个整数n。
接下来是一个n*n的01矩阵,第i行第j列为1表示习得天赋j的一个前置天赋为i。
数据保证第一列和主对角线全为0。
n<=300
接下来是一个n*n的01矩阵,第i行第j列为1表示习得天赋j的一个前置天赋为i。
数据保证第一列和主对角线全为0。
n<=300
Output
第一行一个整数,问题所求的方案数。
题解:
这是一道有向图生成树计数的裸题……
没什么好说的。
CODE:
1 #include<iostream> 2 #include<cstdio> 3 using namespace std; 4 5 #define mod 1000000007 6 char s[305]; 7 int n,a[305][305],ans=1; 8 9 int qpow(int x,int y){ 10 int ans=1; 11 while(y){ 12 if(y&1)ans=1LL*ans*x%mod; 13 y>>=1,x=1LL*x*x%mod; 14 } 15 return ans; 16 } 17 18 void Gauss(){ 19 for(int i=2;i<n;i++){ 20 for(int j=i+1;j<=n;j++){ 21 if(a[j][i]==0)continue; 22 if(a[i][i]==0){ 23 putchar('0'); 24 exit(0); 25 } 26 int tmp=1ll*a[j][i]*qpow(a[i][i],mod-2)%mod; 27 for(int k=i;k<=n;k++) 28 a[j][k]=(a[j][k]-1ll*a[i][k]*tmp%mod+mod)%mod; 29 } 30 } 31 } 32 33 int main(){ 34 scanf("%d",&n); 35 for(int i=1;i<=n;i++){ 36 scanf("%s",s+1); 37 for(int j=1;j<=n;j++) 38 if(s[j]=='1')a[j][i]=-1,a[j][j]++; 39 } 40 for(int i=2;i<=n;i++) 41 for(int j=2;j<=n;j++) 42 if(a[i][j]<0)a[i][j]+=mod; 43 Gauss(); 44 for(int i=2;i<=n;i++) 45 ans=1LL*ans*a[i][i]%mod; 46 printf("%d",ans); 47 }