题意:大家都知道矩形是不稳定的,会变成平行四边形,但是可以在矩形对角线加边,通过构成三角形使这个矩形稳定下来。给一n*m的矩形,可以在单位矩形里加两种对角线(从左上到右下,从左下到右上两种),或者不加对角线,问使这个n*m的矩形稳定下来的方案数。
思路:说思路前先膜一膜鸟神,是鸟神教会了我这道题……万分感谢。
多校官方题解是这样说的:
其实题解已经把思路说好了……只不过太简单了吧……蒟蒻很无奈的。
先说题目为什么可以看做求连通二分图数目,接着说具体解法。
一. 为什么可以看做求连通二分图数目
n*m的矩形有如下性质:对于任意一行,这一行的每一条竖边永远保持平行;同样,对于任意一列,这一行的每一条横边永远保持平行。如题目里的图片所示:
当对一个单位格加上一个斜边的时候,这个小的单位格的形状就不能改变,实质上就是组成这个小单位格的横边和竖边保持着一个垂直关系一旦组成这个小单元格的横边和竖边保持了一个垂直关系,那么和这个横边竖边保持平行关系的变也永远保持一个垂直关系。
以此,可以把这个问题变为二分连通图的计数问题,见下图(自己纯手绘的大家轻喷啊……):
橙色的斜边,使得组成(1,1)格的上下两边(图中的黄边)和第一行的所有竖边(图中的红边)永远保持垂直关系,以及(1,1)格的左右两边和第一列的所有横边永远保持垂直关系(为了不让图太混乱,就没有标出这几条边)。对应到二分图,也是橙色的那条边,意思是第一行和第一列的所有边(因为处在同一行的边永远互相平行,同一列的边也是如此)都保持垂直关系。
要使矩形形状不会发生变化,其实就是任一行和任一列都保持垂直关系。对应到二分图中,就是由行列的点集组成的二分图是连通 的。
二.如何通过计算一个二分图的连通方案数求解
首先,一个n*m的矩阵,由n*m个单位矩阵组成,每个单位矩阵可以加两种对角线(从左上到右下,从左下到右上两种),或者不加对角线,共3种选择,则一个n*m矩阵的总方案数是3^(n*m),只要从所有方案中,除去不合法的方案数,就是合法的方案数。这么做的原因是不合法的方案数更容易求(只要二分图是不连通 的,这个方案就是不合法的)。
设dp[n][m]为使n*m矩阵固定下来的合法方案数。
接着,固定一个点(固定这个点是为了防止计数时的重复计算,待会讲完读者回来想想就明白了),为了方便说明,设这个点是n个点中的第一个点(不是m个点中的点)。
枚举这个点所处连通块的情况,只要这个连通块不包含二分图中所有的点,则当前的二分图一定是不连通的,一定是非法方案。
于是状态转移方程是:
其中各元素的取值范围是:1<=n<=10,0<=m<=10,1<=i<=n,0<=j<=m。
试着用一幅图说明这个方程:
n中的点1固定,当连通块大小为i,j时,如何计算非法方案数,从n-1个点中选出i-1个点(因为点1已经被固定了),从m个点中选出j个点,用这i,j个点组成连通块的数量就是
,i+j大小的连通块有dp[i][j]个合法的存在方案,同时,下面的n-i个点和m-j个点有
个组合方式。
重复一下:
固定一个点,枚举这个点所处连通块的情况,只要这个连通块不包含二分图中所有的点,则当前的二分图一定是不连通的,一定是非法方案。
一些注意事项:
在枚举i,j的时候,当i==n,j==m的时候,就要停止,不可以让自己减自己;
dp[1][0]应该设为1,代表全二分图没有一条边的情况,因此dp[2][0]……dp[n][0]都得设为0,不然会有重复计算发生,不过我们不需要这样做,因为在枚举过程中,dp[2][0]会减去dp[1][0]时的情况,使自己变回0。
注意事项可能说的不好,详见代码吧
//不初始化你给我死全家
#include<iostream>
#include<cstdio>
#include<cmath>
#include<cstring>
#include<algorithm>
#include<cstdlib>
using namespace std;
#define MS(x,y) memset(x,y,sizeof(x))
#define MP(x,y) make_pair(x,y)
#define lowbit(x) (x&(-x))
typedef long long LL;
inline void fre1(){freopen("input.txt","r",stdin);/*freopen("output.txt","w",stdout);*/}
inline void fre2(){fclose(stdin);/*fclose(stdout);*/}
const int MAXN=30+5;
const double EPS=1e-8;
const int MOD=1e9+7;
LL fact[MAXN*MAXN],C[MAXN][MAXN],dp[MAXN][MAXN];
int main()
{
int n=11;
fact[0]=1;
for(int i=1;i<=n*n;++i) fact[i]=fact[i-1]*3%MOD;
C[0][0]=1;
for(int i=1;i<MAXN;++i){
C[i][0]=C[i][i]=1;
for(int j=1;j<i;++j)
C[i][j]=(C[i-1][j]+C[i-1][j-1])%MOD;
}
for(int i=1;i<=10;++i){
for(int j=0;j<=10;++j){
dp[i][j]=fact[i*j];
if(i==1&&j==0) dp[i][j]=1;
for(int ii=1;ii<=i;++ii){
for(int jj=0;jj<=j;++jj){
if(i==ii&&j==jj) continue;
dp[i][j]-=C[i-1][ii-1]*C[j][jj]%MOD*dp[ii][jj]%MOD*fact[(i-ii)*(j-jj)]%MOD;
dp[i][j]=(dp[i][j]%MOD+MOD)%MOD;
}
}
}
}
int m;
while(~scanf("%d%d",&n,&m)) printf("%I64d\n",dp[n][m]);
return 0;
}