通過看題解畫圖可以發現:
不論怎麼轉,一列裏的橫邊/一行裏的豎邊始終平行
當我們加固一個格子時,會讓它所在的這一行的豎邊和這一列的橫邊保證垂直
而我們的目標是求所有豎邊和橫邊都保證垂直的方案數
把一行裏的所有豎邊看成一個點,把一列裏的所有橫邊看成一個點。一共$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\n",f[n][m]); 48 } 49 return 0; 50 }