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

来源:互联网 发布:sql server在哪下载 编辑:程序博客网 时间:2024/05/21 20:25

[题目链接]

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

[分析]
如果每条边只有1种选择,那就变成了Project Euler 434,所以我们先来考虑这个问题
对于n,m点的二分图,若不考虑连通性的限制,那么总方案数为2nm
只要再减去不连通的情况即可
如何不重复不遗漏的计算出不连通的方案数呢?
这里本弱学习了一种看起来十分靠谱的递推方案

设有n,m点的两个点集(A,B),使其连通的连边方案数为f(n,m)
A中选i个点,B中选j个点 的方案数为CinCjm,使其连通的方案数为f(i,j)
对于每一种选法,设两个点集剩余部分为(A,B),只要保证AB间以及AB间没有任何边,整个二分图就不连通,所以AB间可以任意连边,方案数为2(ni)(mj)
枚举i和j,依次减去即可
但注意到,这样枚举是会有重复计算的
比如,在选取前i个点与前j个点时,对应于取后n-i点和后m-j点,会有重复统计的情况
为了避免这种情况,我们可以在A中固定取第一个点,在剩余的n-1个点中取i-1个点,而B的取法不变,这样做就能做到不重复不遗漏
因为所有不包含A中第一个点的方案都被对称的统计了,而且这样选取是不会出现重复的

f(n,m)=2nm0<i<=n0<=j<=mi+j<n+mf(i,j)Ci1n1Cjm2(ni)(mj)

在加入边的选择后, 只需要多存一维边数,枚举的时候也多枚举一维边数即可
任意连k条边的方案数为Cknm
f(n,m,k)表示边数为k,两个点集大小为n和m的连通二分图数量

f(n,m,k)=Cknm0<i<=n0<=j<=m0<=s<=ki+j<n+mf(i,j,s)Ci1n1CjmCks(ni)(mj)

最终答案为
ans=i=0nmf(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[N][N][M] , p2[M] ;LL C[M][M] ;int ans[N][N] ;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[n][m][k] ) return f[n][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[n][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)=3nm0<i<=n0<=j<=mi+j<n+mf(i,j)Ci1n1Cjm3(ni)(mj)

这样之后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[N][N] , p3[M] ;LL C[M][M] ;int ans[N][N] ;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[n][m] ) return f[n][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[n][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 ;}
0 0
原创粉丝点击