かっつのメモ帳

主に競プロ 時々日記

IPSC 2010 L. Lovely stamps

問題のリンク

問題概要

$N$ 種類の1ドル切手と $ M $ 種類の2ドル切手が販売されている。丁度 $K$ ドル使うような切手の買い方の総数を素数 $ P $ で割った余りを求めよ。

ただしどの種類の切手も無限に存在するとして良い。

制約

  • $1 \leq N,M \leq 300$
  • $1 \leq K \leq 10^{12}$
  • $3 \leq P \leq 10^{6}$
  • $P$ は素数

前提知識

導入

数列 $ \{a _ {n} \} $ の第 $n$ 項 $ a _ {n} $ を $x ^ {n}$ の係数とする形式的冪級数

$$ F(x) = a _ {0} + a _ {1} x + a _ {2} x^{2} + \cdots + a _ {n} x^{n} + \cdots $$

を $ \{a _ {n} \} $ の(通常型)母関数と呼ぶ。

代表的な母関数の例として、数列 $\{1,1,\ldots , 1 , \ldots \}$ では

$$ 1 + x + x^{2} + \cdots + x ^ {n} + \cdots = \sum_{n=0}^{\infty} x^{n} = \frac{1}{1-x}$$

のように表される。

形式的冪級数は収束性の議論を無視する(詳しく勉強していないので適切な表現ではない可能性があります)ため、ここでは $ x $ は単なる変数に過ぎず、次のように様々な値への置換を考えることができる。

$x \rightarrow a x(a \neq 0)$ と置換することで、等比数列 $\{a^{0},a^{1},\ldots , a^{n} , \ldots \}$ では

$$ \sum_{n=0}^{\infty} (ax)^{n} = \frac{1}{1-ax}$$

同様に $x \rightarrow x^{k}(k \in \mathbb{N})$ への置換では

$$ \sum _ {n=0}^{\infty} x^{k n}=\frac{1}{1-x^{k}} $$

が得られる。

微分

形式的冪級数 $F=\sum _ {n=0}^{\infty} a_{n} x^{n}$ に対して、形式微分 $ F^{\prime} $ を次のように定義する。

$$ F^{\prime}:=\sum _ {n=1}^{\infty} n \cdot a _ {n} x^{n-1} $$

負の二項係数

$$ \sum _ {n=0}^{\infty}\left(\begin{array}{c} m+n-1 \\ n \end{array}\right) x^{n}=\frac{1}{(1-x)^{m}} $$

が成立する。

証明

組み合わせ的解釈でも導出ができるが、ここでは微分を用いた帰納法で示す。

$ m=d $ での成立を仮定し、$ m=d+1 $ について考える。

$$ \sum _ {n=0}^{\infty}\left(\begin{array}{c} d+n-1 \\ n \end{array}\right) x^{n}=\frac{1}{(1-x)^{d}} $$

の両辺を微分すると、

$$ \sum _ {n=1}^{\infty} \left(\begin{array}{c} d+n-1 \\ n \end{array}\right) n x^{n-1} = \sum _ {n=0}^{\infty} \left(\begin{array}{c} d+n \\ n+1 \end{array}\right) (n+1) x^{n} = d \cdot \frac{1}{(1-x)^{d+1}} $$

より、

$$ \begin{eqnarray} \frac{1}{(1-x)^{d+1}} &=& \sum _ {n=0}^{\infty} \left(\begin{array}{c} d+n \\ n+1 \end{array}\right) \frac{n+1}{d} x^{n} \\ &=& \sum _ {n=0}^{\infty} \left(\begin{array}{c} d+n \\ n \end{array}\right) x^{n} \end{eqnarray} $$

となり、成立が確認できる。


この関係は二項係数から導かれる等式

$$ \sum _ {n=0}^{\infty}\left(\begin{array}{c} m \\ n \end{array}\right) (-x)^{n}= (1-x)^{m} $$

に対応させてしばしば負の二項係数と呼ばれる。

また、左辺に登場する二項係数 $\tbinom{m+n-1}{n}$ は、$ m $ 種類のものから重複を許して $ n $ 個選びとる、重複組合せの総数に対応する。

解法

求める切手の買い方の総数は、

$$ \begin{eqnarray} \frac{1}{(1-x)^{n} (1- x^{2})^{m}} &=& \frac{(1+x) ^ {n}}{(1-x)^{n} (1- x^{2})^{m} (1+x) ^ {n}} \\ &=& \frac{(1+x) ^ {n}}{(1- x^{2})^{n+m}} \\ &=& \sum _ {a=0}^{n} \dbinom{n}{a} x^{a} \sum _ {b=0}^{\infty} \dbinom{ n + m + b - 1}{ n + m - 1} x^{2b} \end{eqnarray} $$

の $ x^{K} $ の係数に等しい。

$a$ を固定して考えると、$b = \frac{K-a}{2}$ となりこれが整数になるような $a$ に対して、

$$ \sum _ {a=0}^{n} \dbinom{n}{a} \dbinom{ n + m + \frac{K-a}{2} - 1}{ n + m - 1} $$

を求めれば良い。

$ K $ が大きく $ P $ が小さいことを利用して Lucasの定理を用いるか、$ O(N+M) $ のループを回すかのいずれかで二項係数を計算する。

manabitimes.jp

実装

#include <bits/stdc++.h>
using namespace std;
typedef long long int ll;

ll mod_pow(ll x,ll n,ll Mod){
    x%=Mod;
    ll res=1;
    while(n>0){
        if(n&1LL)res=res*x%Mod;
        x=x*x%Mod; n>>=1LL;
    }
    return res;
}

struct perm{
private:
    int sz;
    ll mod;
public:
    vector<ll> p,invp;
    perm(int n,ll Mod){
        sz=n+1;mod=Mod;
        p.resize(sz),invp.resize(sz);
        p[0]=1;
        for(int i=1;i<=sz-1;i++){
            p[i]=p[i-1]*i%mod;
        }
        invp[sz-1]=mod_pow(p[sz-1],mod-2,mod);
        for(int i=sz-2;i>=0;i--){
            invp[i]=invp[i+1]*(i+1)%mod;
        }
    }
    ll comb(ll x,ll y){
        if(x<y||y<0)return 0;
        return (p[x]*invp[x-y]%mod)*invp[y]%mod;
    }
    ll comb(ll x,ll y,ll p){ // Lucasの定理
        ll res=1;
        while(x>0 or y>0){
            (res*=comb(x%p,y%p))%=p;
            x/=p; y/=p;
        }
        return res;
    }
};

int main(){
    int n,m; cin >> n >> m;
    ll k,p; cin >> k >> p;
    perm P(p-1,p);
    ll res=0;
    for(int i=0;i<=n;i++){
        if((k-i)%2==0){
            ll tmp=P.comb(n,i,p)*P.comb(n+m+(k-i)/2-1,n+m-1,p)%p;
            (res+=tmp)%=p;
        }
    }
    cout << res << endl;
}