[二分图] hdu 5729 Rigid Frameworks
@(ACM题目)[二分图]
这道题我一开始想的是要让相邻的行之间不能相对移动、相邻的列之间不能相对移动,而某个格子处的对角线只要存在,该格子的平行的对边所在的行(列)就不能相对移动了,因此每一行、每一列至少有一个格子有对角线(与对角线方向无关)。
然后想到这里就想不动了。。。于是看了题解,发现对于rigid可以有另一种理解,即一个格子的对角线存在,使相邻两边的夹角不能发生变化。我一开始想的是行与行(列与列)之间的关系,而题解中则考虑了行与列之间的关系。这有一个明显的优势——可以以行为一类点、列为另一类点构造二分图了!!!令左边的点代表行,右边的点代表列,第i行与第j列有对角线则为左i点与右j点右边相连。
要使整体rigid,任意一行与任意列都的角度都不能变,对应的二分图是连通的。这是因为,若i1行与j1列角度不变,j1列与i2行角度不变,i2行与j2行角度不变,则i1行与j2列角度不变。
dp[i][j]代表i*j的矩阵的方案数,先正向考虑计算方法。对于二分图的一种连通情况,若其有s条边,则这个二分图对应 2s 种方案(每个对角线都有两个方向)。对二分图的每一种连通情况对应的方案数累和可得答案。可以发现正向考虑难以计算。
于是反向考虑,用所有方案数减去不连通的方案数。
- 总方案数为
3i+j
。
- 对于左边的某个特定的点a,其所在的连通块不包含所有的i+j个点
⇔
整个二分图不连通。所以考虑该特定点(不妨设为左边第一个点)所在的连通块包含左边i1个点和右边的j1个点,只要不是i1=i且j1=j,整个二分图就是不连通的,对应的不连通方案数为
Ci1−1iCj1jdp[i1][j1]⋅3(i−i1)(j−j1)
。左边除了第一个点还需要选i1-1个,右边在j个中选j1个,组成的i1+j1大小的连通块对应方案数dp[i1][j1] ,剩下的(i-i1)(j-j1)个点随意(无对角线,或加两种对角线中的一种)。注意当没有点与左边第一个点相连时的情况不要落掉,共
3(i−1)j
中方案。
#include<iostream>
#include<cstdio>
#include<string>
#include<cstring>
#include<cstdlib>
#include<algorithm>
#include<cmath>
#include<map>
#include<vector>
#include<set>
using namespace std;
const int MOD = 1e9+7;
const int maxn = 12;
const int maxn2 = 105;
typedef long long ll;
ll com[maxn][maxn], pow3[maxn2], dp[maxn][maxn];
int main()
{
for(int i = 0; i < maxn; i++)
{
com[i][0] = 1;
for(int j = 1; j <= i; j++)
com[i][j] = com[i][j-1]*(i-j+1)/j;
}
pow3[0] = 1;
for(int i = 1; i < maxn2; i++)
pow3[i] = pow3[i-1]*3%MOD;
dp[1][1] = 2;
for(int i = 2; i < maxn; i++) dp[1][i] = dp[i][1] = dp[i-1][1]*2%MOD;
for(int i = 2; i < maxn; i++)
for(int j = 2; j < maxn; j++)
{
dp[i][j] = pow3[i*j]-pow3[(i-1)*j];
for(int i1 = 1; i1 <= i; i1++)
for(int j1 = 1; j1 <= j; j1++)
if(i1!=i || j1!=j)
{
dp[i][j] -= (com[i-1][i1-1]*com[j][j1]*dp[i1][j1])%MOD*pow3[(i-i1)*(j-j1)]%MOD;
dp[i][j] = (dp[i][j]%MOD+MOD)%MOD;
}
}
int n, m;
while(cin>>n>>m) cout<<dp[n][m]<<endl;
return 0;
}