2項係数の計算でオーバーフローを防ぐ

/* 分数 m/n を通分する */
int reduce(int *m, int *n)
{
  int g = gcd(*m, *n);
  *m /= g;
  *n /= g;
}
int enum_combination(int n, int k)
{
  /* C(n,k) = n / k * C(n-1,k-1) というように求める */
  /* 答えがオーバーフローしない範囲の k は、それほど大きくない (k <- n-k の置き換えにより)
     ので、再帰はそれほど深くならない
  */
  if ( n - k < k ) {
    /* n から n-k 個と、n から k 個 の選びかたは同じ */
    return enum_combination(n, n-k);
  } else if ( k == 1 ) {
    return n;
  } else {
    int a = enum_combination(n-1, k-1);
    /* n * a / k は整数。k の因数は、n, a の両方に含まれている可能性がある */
    reduce(&n, &k);             /* n,k の共通因数をなくす */
    assert( a % k == 0 );
    a /= k;                     /* a,k の共通因数をなくす */
    return a * n;
  }
}

正当性については略。

オーバーフローについて。
計算されるべき結果が M 以下だとする。
このとき、計算の途中で ans または div が M を越えることがあるか?

n または k が小さくなると、選びかたの数は小さくなる。
計算の途中で呼ばれる enum_combination が返す値は、戻りのたびに増えるので、
一度 M を越えると、そのあとずっと越える。

1 <= n, 0 <= k <= n で n,k がともに整数のとき、
n * (n-1) * .. * (n-(k-1)) / ( k * (k-1) * .. * 1 ) が整数であることについて。
分子分母の右から j 項の分数に注目し、j に関する帰納法で証明できる。
j = 1 のとき自明。
分子分母で右から j 項分の分数の計算結果を m_j とおき、m_j が整数だと仮定する。(帰納法の仮定)
m_{j+1} = m_j * (n-(k-1)-(j-1)) / (j + 1)
↑の分子はもともと、連続する j+1 個の数の積なので、その j+1 個の数の中にはちょうど 1 つ、 j+1 の倍数が含まれている。
つまり、この分子は j+1 の倍数である。
m_j が整数だと仮定しているので、分母は j+1 のみである。
よって、m_{j+1} は整数。