51nod1835 完全图

题意:输入n,m(n,m<500),问有n的点且联通块为m个的图有多少

题解:

dp[i][j]代表取前i个形成j个联通块的个数
第i个单独考虑,可以枚举联通块的大小K:[1, i-j+1]
要保证枚举的时候方案不会重复,那么不能写作c(i, k)
这样dp[i1][j]和dp[i2][j]会有重复的部分
所以这里固定下来第i个是必须要取的
那么方案数就是c(i-1, k-1)
所以可以得到递推式
for(int k=1;k<=i-j+1;k++)
dp[i][j] += c(i-1, k-1)*dp[i-k][j-1]*dp[k][1];
dp[k][1]要单独计算,它的意义是k个数形成一个联通块的方案数:
dp[k][1] = 2^(k*(k-1)/2);
for(int i=2;i<=k;i++) dp[k][1] -= dp[k][i];
注意题目要求至少一条边,所以当m==1时,答案要减1

要预处理

#include <bits/stdc++.h>
#define ll long long
#define maxn 510
using namespace std;
const ll mod = 998244353;
ll c[maxn][maxn], n, m, dp[maxn][maxn];
void init(){
    for(ll i=0;i<=500;i++){
        c[i][0] = c[i][i] = 1;
        for(ll j=1;j<i;j++)
            c[i][j] = (c[i-1][j-1]+c[i-1][j])%mod;
    }
}
ll f(ll a,ll b){
    ll ans = 1;
    a = a%mod;
    while(b){
        if(b&1) ans = ans*a%mod;
        a = a*a%mod;
        b >>= 1;
    }
    return ans;
}
int main(){
    init();
    scanf("%lld%lld", &n, &m);
    for(ll i=1;i<=n;i++){
        for(ll j=2;j<=i;j++)
            for(ll k=1;k<=i-j+1;k++)
                dp[i][j] = (dp[i][j]+c[i-1][k-1]*dp[i-k][j-1]%mod*dp[k][1]%mod)%mod;
        dp[i][1] = f(2, (i*(i-1)/2));
        for(ll j=2;j<=i;j++)
            dp[i][1] = (dp[i][1]+mod-dp[i][j])%mod;
    }
    if(m == 1) printf("%lld\n", dp[n][1]-1);
    else printf("%lld\n", dp[n][m]);
    return 0;
}

 

posted on 2018-02-02 17:45  2855669158  阅读(168)  评论(0编辑  收藏  举报

导航