(HDU6397)2018 Multi-University Training Contest 8 - 1001 - Character Encoding - (组合数容斥 | 母函数)

题目链接http://acm.hdu.edu.cn/showproblem.php?pid=6397

题意:有n个数字0~n-1,问任取m个使得总和为k的方案数。(可以重复取)

解析:此题可以用母函数来解,也可以通过组合数容斥来解。

组合数容斥解法:

我们将问题转化为:X个相同的球放入Y个不相同的盒子中,要求每个盒子至多N个球至少0个球,共有多少种不同的放法?正好是知乎上的一个话题https://www.zhihu.com/question/65668587,图中X=k,Y=m,N=n-1;

(图片截图自知乎,侵删)

(以下题解来自dls直播,录屏重播地址https://www.bilibili.com/video/av29454041

母函数解法:

  • 首先可得母函数:(1+x^1+x^2+...+x^(n-1))^m,答案就是此多项式中x^k的系数。
  • 由等比数列公式(1+x^1+x^2+...+x^(n-1)) = (1-x^n) / (1-x);则(1+x^1+x^2+...+x^(n-1))^m=(1-x^n)^m / (1-x)^m=(1-x^n)^m * (1-x)^-m;
  • 由多项式定理(1-x^n)^m= sum( i=0 to m  (-1)^i*C(m,i)*x^(i*n) );
  • 又由某公式得(1-x)^-m= sum( i>=0  x^i*C(m-1+i,m-1) );
  • 母函数可化为:sum( i=0 to m  (-1)^i*C(m,i)*x^(i*n) )  *  sum( i>=0  x^i*C(m-1+i,m-1) );
  • 此多项式中x^k的系数为:sum( i>=0&&i*n<=k  (-1)^i * C(m,i) * C(m-1+k-n*i,m-1);

容斥解法

  • 由于问题转化为将总和k为m份,每份的值xi>=0&&xi<=n-1的方案数。
  • 首先有经典组合公式:x1+x2+...+xm=k (xi>=0) 解的个数为C(k+m-1,m-1)。
  • 那么我们相用如上公式解决本题,发现上述公式没有此题中xi<=n-1的条件,那我们就求出总方案,容斥掉不合法的方案(即存在xi>=n-1)。
  • 对于有t个不合法的xi>=n(假设k>=t*n),我们怎么求这个不合法的方案数(即x1+x2+...+xm=k (存在xi>=n) 方案数),还是用经典组合公式,先做变换:如果xi<n则xi'=xi,否则xi'=xi-n,则x1+x2+...+xm=k (存在xi>=n) ==>  x1'+x2'+...+xm'=k-t*n (所有xi>=0);方案数就是C(k-t*n+m-1,m-1)。
  • 再进行容斥就是0个不合法 - 1个不合法 + 2个不合法 - 3个不合法。。。。
  • 公式如下:                                       容斥系数                  变量选法        c个不合法方案数
  • -> c个不合法( c>=0&&c*n<=k )        (-1)^c            *          C(m,c)     *     C(k-c*n+m-1,m-1)   

发现两种方法所得公式相同。

代码

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll mod=998244353;
const ll M=2*1e5+5;
ll n,m,k;

ll fact[M],ifact[M];//fact[i]是i的阶乘,ifact[i]是阶乘的除法逆元,两者用于求组合数
ll pow_mod(ll n,ll k,ll mod)//快速幂求n^k余m的结果
{
    ll res=1;
    n=n%mod;
    while(k>0)
    {
        if(k&1)
            res=res*n%mod;
        n=n*n%mod;
        k>>=1;
    }
    return res;
}
void init()//初始化
{
    fact[0]=ifact[0]=1;
	for(int i=1;i<M;++i)
    {
        fact[i]=(fact[i-1]*i)%mod;
		ifact[i]=pow_mod(fact[i],mod-2,mod);
	}
	ifact[M-1]=pow_mod(fact[M-1],mod-2,mod);
	for(int i=M-1;i>0;i--)
        ifact[i-1]=ifact[i]*i%mod;
}

ll C(ll n,ll m)//求组合数
{
    if(n<m)
        return 0;
    return  fact[n]*ifact[m]%mod*ifact[n-m]%mod;
}

int main()
{
    init();
    int T;
    scanf("%d",&T);
    while(T--)
    {
        scanf("%I64d%I64d%I64d",&n,&m,&k);
        ll X=k,Y=m,N=n-1;
        ll ans=C(X+Y-1,Y-1);
        for(ll t=1;t<=X/(N+1);t++)
        {
            if((t&1)==0)
            {
                ans=(ans+C(Y,t)*C(X+Y-1-t*(N+1),Y-1)%mod)%mod;
            }
            else
            {
                ans=(ans-C(Y,t)*C(X+Y-1-t*(N+1),Y-1)%mod+mod)%mod;
            }
        }
        printf("%I64d\n",ans%mod);
    }
    return 0;
}

猜你喜欢

转载自blog.csdn.net/sdau20163942/article/details/81736842