51nod 1835 - 完全图 - [dp][组合数公式][快速幂]

题目链接:https://www.51nod.com/onlineJudge/questionCode.html#!problemId=1835

基准时间限制:1 秒
空间限制:131072 KB
 
初始有n个点,任意两个点之间有一条无向边,现在要移除一些无向边(至少一条),问移除后有恰好m个连通块的方案数是多少。
两个方案不同当且仅当存在至少一条无向边在某个方案中被移除,但是在另一个方案中没被移除。
答案可能很大请模一个998,244,353。
 
Input
第一行读入n,m。
1<=m<=n<=500
Output
第一行输出方案数。
Input示例
3 2
Output示例
3

 

题解:

 

AC代码:

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll MOD = 998244353;
const int maxn = 503;

ll C[maxn][maxn];
void Cmn()//求组合数
{
    for(int i=0;i<maxn;i++)
    {
        C[i][0]=C[i][i]=1;
        for(int j=1;j<i;j++) C[i][j]=(C[i-1][j-1]+C[i-1][j])%MOD;
    }
}

ll fpow(ll a,ll b)
{
    ll r=1,base=a%MOD;
    while(b)
    {
        if(b&1) r*=base,r%=MOD;
        base*=base;
        base%=MOD;
        b>>=1;
    }
    return r;
}

ll n,m;
ll f[maxn][maxn];
int main()
{
    Cmn();
    scanf("%d%d",&n,&m);

    f[1][1]=1;
    for(int i=2;i<=n;i++)
    {
        for(int j=2;j<=i;j++)
        {
            f[i][j]=0;
            for(int k=1;k<=i-(j-1);k++)
            {
                f[i][j] += (C[i-1][k-1]*f[k][1]%MOD)*f[i-k][j-1] %MOD;
                f[i][j] = f[i][j] % MOD;
            }
        }

        f[i][1] = fpow(2,i*(i-1)/2);
        for(int k=2;k<=i;k++) f[i][1] = (f[i][1] - f[i][k] + MOD) %MOD;
    }

    if(m==1) printf("%lld",f[n][1]-1);
    else printf("%lld",f[n][m]);
}        

 

有几个需要注意的点:

1、

对于 for(int k=2;k<=i;k++) f[i][1] = (f[i][1] - f[i][k] + MOD) %MOD; 

考虑f[i][j]都是mod过998244353的数,f[i][1] - f[i][k]有可能为负,需要加上MOD后再%MOD;

2、

pow( 2 , i*(i-1)/2 )显然爆longlong,要用矩阵快速幂算;

3、

题目中写“移除一些无向边(至少一条)”,所以当m等于1的时候,不能移除边,就没有方案。

posted @ 2018-01-16 20:55  Dilthey  阅读(459)  评论(0编辑  收藏  举报