【bzoj4031】[HEOI2015]小Z的房间 && 【bzoj4894】天赋 (矩阵树定理)

来两道矩阵树模板:

T1:【bzoj4031】[HEOI2015]小Z的房间

Description

你突然有了一个大房子,房子里面有一些房间。事实上,你的房子可以看做是一个包含n*m个格子的格状矩形,每个格子是一个房间或者是一个柱子。在一开始的时候,相邻的格子之间都有墙隔着。
你想要打通一些相邻房间的墙,使得所有房间能够互相到达。在此过程中,你不能把房子给打穿,或者打通柱子(以及柱子旁边的墙)。同时,你不希望在房子中有小偷的时候会很难抓,所以你希望任意两个房间之间都只有一条通路。现在,你希望统计一共有多少种可行的方案。

Input

第一行两个数分别表示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

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 }
posted @ 2018-07-30 09:33  ezoiLZH  阅读(179)  评论(0编辑  收藏  举报