[二分图] hdu 5729 Rigid Frameworks

13 篇文章 0 订阅
5 篇文章 0 订阅

[二分图] 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,整个二分图就是不连通的,对应的不连通方案数为 Ci11iCj1jdp[i1][j1]3(ii1)(jj1) 。左边除了第一个点还需要选i1-1个,右边在j个中选j1个,组成的i1+j1大小的连通块对应方案数dp[i1][j1] ,剩下的(i-i1)(j-j1)个点随意(无对角线,或加两种对角线中的一种)。注意当没有点与左边第一个点相连时的情况不要落掉,共 3(i1)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;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值