您的位置:首页 > 其它

HDU 5729 Rigid Frameworks(组合计数,递推)

2016-07-21 09:25 429 查看
[题目链接]

[题意]

赛上就没读懂过题意…

通过转换将问题变为求两个点集大小分别为n和m的连通二分图个数,其中每条边可以有2种选择(主副对角线)

[分析]

如果每条边只有1种选择,那就变成了Project Euler 434,所以我们先来考虑这个问题

对于n,m点的二分图,若不考虑连通性的限制,那么总方案数为2n∗m

只要再减去不连通的情况即可

如何不重复不遗漏的计算出不连通的方案数呢?

这里本弱学习了一种看起来十分靠谱的递推方案

设有n,m点的两个点集(A,B),使其连通的连边方案数为f(n,m)

从A中选i个点,B中选j个点 的方案数为Cin∗Cjm,使其连通的方案数为f(i,j)

对于每一种选法,设两个点集剩余部分为(A′,B′),只要保证A′与B间以及A与B′间没有任何边,整个二分图就不连通,所以A′与B′间可以任意连边,方案数为2(n−i)(m−j)

枚举i和j,依次减去即可

但注意到,这样枚举是会有重复计算的

比如,在选取前i个点与前j个点时,对应于取后n-i点和后m-j点,会有重复统计的情况

为了避免这种情况,我们可以在A中固定取第一个点,在剩余的n-1个点中取i-1个点,而B的取法不变,这样做就能做到不重复不遗漏

因为所有不包含A中第一个点的方案都被对称的统计了,而且这样选取是不会出现重复的

f(n,m)=2n∗m−∑0<i<=n0<=j<=mi+j<n+mf(i,j)∗Ci−1n−1∗Cjm∗2(n−i)∗(m−j)

在加入边的选择后, 只需要多存一维边数,枚举的时候也多枚举一维边数即可

任意连k条边的方案数为Ckn∗m

f(n,m,k)表示边数为k,两个点集大小为n和m的连通二分图数量

f(n,m,k)=Ckn∗m−∑0<i<=n0<=j<=m0<=s<=ki+j<n+mf(i,j,s)∗Ci−1n−1∗Cjm∗Ck−s(n−i)∗(m−j)

最终答案为

ans=∑i=0n∗mf(n,m,i)∗2i%mod

[代码]

#include <bits/stdc++.h>
using namespace std ;
const int N = 11 ;
const int M = 101 ;
const int mod = 1e9 + 7 ;
typedef long long LL ;

int n , m ;
LL f

[M] , p2[M] ;
LL C[M][M] ;
int ans

;

void init()
{
C[0][0] = p2[0] = 1 ;
for( int i = 1 ; i < M ; i++ )
{
p2[i] = p2[i-1]*2%mod ;
C[i][i] = C[i][0] = 1 ;
for( int j = 1 ; j < i ; j++ )
C[i][j] = ( C[i-1][j-1] + C[i-1][j] ) % mod ;
}
memset(f,-1,sizeof(f)) ;
}

LL dfs( int n , int m , int k )
{
if( n < m ) swap(n,m) ;
if( ~f
[m][k] ) return f
[m][k] ;
LL sum = 0 ;
for( int i = 1 ; i <= n ; i++ )
{
for( int j = 0 ; j <= m ; j++ )
{
if( i == n && j == m ) continue ;
for( int s = 0 ; s <= k ; s++ )
{
sum += dfs(i,j,s)*C[n-1][i-1]%mod*C[m][j]%mod*C[(n-i)*(m-j)][k-s]%mod ;
}
}
}
return f
[m][k] = ( C[n*m][k] - sum%mod + mod ) % mod ;
}

int main()
{
init() ;
while( ~scanf( "%d%d" , &n , &m ) )
{
LL ans = 0 ;
for( int i = 0 ; i <= n*m ; i++ )
{
ans += dfs(n,m,i)*p2[i]%mod ;
}
printf( "%I64d\n" , ans%mod ) ;
}
return 0 ;
}


[更新]

发现自己简直是zz

根本不需要再加一维边数

每个格子的选择就3种,不连,连主对角线,连副对角线

所以只需要把PE 434 中的2n改为3n

f(n,m)=3n∗m−∑0<i<=n0<=j<=mi+j<n+mf(i,j)∗Ci−1n−1∗Cjm∗3(n−i)∗(m−j)

这样之后0ms通过,代码量也少了很多

[代码]

#include <bits/stdc++.h>
using namespace std ;
const int N = 11 ;
const int M = 101 ;
const int mod = 1e9 + 7 ;
typedef long long LL ;

int n , m ;
LL f

, p3[M] ;
LL C[M][M] ;
int ans

;

void init()
{
C[0][0] = p3[0] = 1 ;
for( int i = 1 ; i < M ; i++ )
{
p3[i] = p3[i-1]*3%mod ;
C[i][i] = C[i][0] = 1 ;
for( int j = 1 ; j < i ; j++ )
C[i][j] = ( C[i-1][j-1] + C[i-1][j] ) % mod ;
}
memset(f,-1,sizeof(f)) ;
}

LL dfs( int n , int m )
{
if( n < m ) swap(n,m) ;
if( ~f
[m] ) return f
[m] ;
LL sum = 0 ;
for( int i = 1 ; i <= n ; i++ )
{
for( int j = 0 ; j <= m ; j++ )
{
if( i == n && j == m ) continue ;
sum += dfs(i,j)*C[n-1][i-1]%mod*C[m][j]%mod*p3[(n-i)*(m-j)]%mod ;
}
}
return f
[m] = ( p3[n*m] - sum%mod + mod ) % mod ;
}

int main()
{
init() ;
while( ~scanf( "%d%d" , &n , &m ) )
{
printf( "%I64d\n" , dfs(n,m) ) ;
}
return 0 ;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  组合数学-递推