通过看题解画图可以发现:
不论怎么转,一列里的横边/一行里的竖边始终平行
当我们加固一个格子时,会让它所在的这一行的竖边和这一列的横边保证垂直
而我们的目标是求所有竖边和横边都保证垂直的方案数
把一行里的所有竖边看成一个点,把一列里的所有横边看成一个点。一共$n+m$个点
把图看成二分图,左侧$n$个点,右侧$m$个点。加固一个格子相当于在左侧的一个点和右侧的一个点之间连边!
我们的问题变成了求解二分图的连通图个数!
接下来就是很套路的$DP$了
定义$f(a,b)$表示左边$a$个点,右边$b$个点的连通二分图个数
对于连通图问题,我们依然采用常规的“固定思想”,我们固定左侧第一个点
直接求联通很困难,考虑用不合法的情况相减,可得$DP$方程:
$f(a,b)=3^{ab}-sum_{i=0}^{a}sum_{j=0}^{b}f(i,j)C_{a-1}^{i-1}C_{b}^{j}3^{(a-i)(b-j)}$
(注意i=a,j=b是不能转移的)
初值怎么赋需要思考
1 #include <cstdio> 2 #include <cstring> 3 #include <algorithm> 4 #define N1 65 5 #define M1 3605 6 #define ll long long 7 using namespace std; 8 const ll p=1000000007; 9 10 int n,m,T; 11 int pw3[M1],C[N1][N1],f[N1][N1]; 12 13 int main() 14 { 15 int i,j,a,b; n=60; m=60; 16 for(i=1,pw3[0]=1;i<=n*m;i++) pw3[i]=3ll*pw3[i-1]%p; 17 C[0][0]=1; 18 for(i=1;i<=max(n,m);i++) 19 { 20 C[i][0]=C[i][i]=1; 21 for(j=1;j<i;j++) 22 C[i][j]=(C[i-1][j]+C[i-1][j-1])%p; 23 } 24 f[1][0]=1; f[1][1]=2; //pw3[0]=0; 25 for(a=1,b=2;b<=m;b++) 26 { 27 f[a][b]=pw3[a*b]; 28 for(j=0,i=1;j<b;j++) 29 { 30 f[a][b]=(f[a][b]-1ll*f[i][j]*C[a-1][i-1]%p*C[b][j]%p*pw3[(a-i)*(b-j)]%p+p)%p; 31 } 32 } 33 for(a=2;a<=n;a++) 34 { 35 for(b=1;b<=m;b++) 36 { 37 f[a][b]=pw3[a*b]; 38 for(i=1;i<a;i++) 39 for(j=0;j<=b;j++) 40 f[a][b]=(f[a][b]-1ll*f[i][j]*C[a-1][i-1]%p*C[b][j]%p*pw3[(a-i)*(b-j)]%p+p)%p; 41 for(j=0,i=a;j<b;j++) 42 f[a][b]=(f[a][b]-1ll*f[i][j]*C[a-1][i-1]%p*C[b][j]%p*pw3[(a-i)*(b-j)]%p+p)%p; 43 } 44 } 45 while(scanf("%d%d",&n,&m)!=EOF) 46 { 47 printf("%d ",f[n][m]); 48 } 49 return 0; 50 }