Problem:
Score : 700 points

Problem Statement

Takahashi has a tower which is divided into N layers.
Initially, all the layers are uncolored. Takahashi is going to paint some of the layers in red, green or blue to make a beautiful tower.
He defines the beauty of the tower as follows:

The beauty of the tower is the sum of the scores of the N layers, where the score of a layer is A if the layer is painted red, A+B if the layer is painted green, B if the layer is painted blue, and 0 if the layer is uncolored.

Here, A and B are positive integer constants given beforehand. Also note that a layer may not be painted in two or more colors.

Takahashi is planning to paint the tower so that the beauty of the tower becomes exactly K.
How many such ways are there to paint the tower? Find the count modulo 998244353.
Two ways to paint the tower are considered different when there exists a layer that is painted in different colors, or a layer that is painted in some color in one of the ways and not in the other.

Constraints

1 ≤ N ≤ 3×10^5

1 ≤ A,B ≤ 3×10^5

0 ≤ K ≤ 18×10^{10}

All values in the input are integers.

Input

Input is given from Standard Input in the following format:

N A B K

Output

Print the number of the ways to paint tiles, modulo 998244353.

Sample Input 1

4 1 2 5

Sample Output 1

40

In this case, a red layer worth 1 points, a green layer worth 3 points and the blue layer worth 2 points. The beauty of the tower is 5 when we have one of the following sets of painted layers:

1 green, 1 blue

1 red, 2 blues

2 reds, 1 green

3 reds, 1 blue

The total number of the ways to produce them is 40.

Sample Input 2

2 5 6 0

Sample Output 2

1

The beauty of the tower is 0 only when all the layers are uncolored. Thus, the answer is 1.

Sample Input 3

90081 33447 90629 6391049189

Sample Output 3

577742975

Solution in Kotlin (1.0.0) (judge verdict: Runtime Error)

fun main(args: Array<String>) {
    /**
     * 値を組み合わせて特定の値を作ることのできるパターン数数え上げ
     * ・組み合わせは非常に大きくなるので mod 計算
     * ・事前に 階乗の mod 値を計算しておく
     * ・a, b, a+b, 0 と色がある時に a+b を塗る = a,b を同じマスに塗ることができる と視点を変えて考えると O(N) になる! 賢い・・
     *  ー> 異なる色の並べ方 etc を考えず，それぞれの色を独立で塗るパターン数を求めてかけ合わせれば良いだけなので計算量が削減できる
     *
     */

    val (n, a, b, k) = readLine()!!.split(" ").map { it.toLong() }

    val modVal = 998244353L

    initTable(modVal)

    //

    var patternNum = 0L
    for ( i in 0..n) {
        val rest = k - a * i

        // a, b の組み合わせで k が作れる かつ，a + b が 2n 以下の場合
        if ( rest % b == 0L && i + rest/b <= 2*n) {

            // k を作ることのできるパターン数は nCi + nC rest/b
            patternNum += (combinationModWithTable(n, i, modVal) * combinationModWithTable(n, rest/b, modVal)) % modVal
        }
    }

    println(patternNum)
}


/**
 * プロコンで頻出の mod 計算を定義したクラス
 *
 * 参考 : https://qiita.com/drken/items/3b4fdf0a78e7a138cd9a
 */

// 階乗の mod 並びに 逆元の mod を格納するための配列を初期化
private const val MAX =
    1e6.toInt()   // MAX は求める必要がある階乗の部分の計算に対してのみ求める (10^7 とすると RuntimeError になったので注意 メモリが足りなくなっている？ )
private val fact = LongArray(MAX + 1) { -1 }  // n 番目は　ｎ! の mod
private val finv = LongArray(MAX + 1) { -1 }  // n 番目は n! の逆元
private val inv = LongArray(MAX + 1) { -1 }  // n 番目は n! の逆元


/**
 * 階乗のmod 並びに 逆元 テーブルを初期化
 * それぞれの計算には漸化式を用いており，各逆元の計算は O(1) で行われる．フェルマーの小定理も，拡張Euclid の互除法も必要ない OMG
 * mod値が 1e9+7 だと，途中計算で mod を求める前に Long最大値 を超えるので LongArray を使用
 *
 * テーブル全体(N)ではなく，単体で逆元計算を行うときには上記定理を用いた計算を実行すると O(N) -> O(log modVal) に計算量を削減できる
 *
 * <注意>
 * nCk mod p の計算に用いる際の暗黙の前提 : p > n かつ k! (k<=n) のどれとも p が一致しない  -> 「p が n より大きい素数」 だと条件が満たされやすい
 * 例えば modVal 以上の 階乗のmod値は 0 になるので注意 5C2 mod 3 とかを計算しようとすると分子の階乗部分が 0 になって正しく計算できないので注意
 * 正しくは (5*4 / 2*1) mod 3 = 1 だが，この方式だと (5*4*3*2*1 / 2*1*3*2*1) mod 3 = 0  となってしまう
 */
fun initTable(modVal: Long) {

    //初期値設定
    fact[0] = 1
    fact[1] = 1
    inv[0] = 1 //?
    inv[1] = 1
    finv[0] = 1
    finv[1] = 1

    for (i in 2..MAX) {

        // 階乗の mod については普通に掛け算 & mod 計算でOK
        fact[i] = fact[i - 1] * i % modVal

        // 各 i に対する逆元を求めて格納しておく (finv 確認 or 直接計算 )

        // 実装上の工夫
        // 1.modVal を足しているのは inv[i] を常に正の値にするため ( inv[i] は常に modVal を前提とした計算で用いるので modVal が加えられていても影響は無い）
        // 2. 新たな inv[i] を求める時に % modVal をしているのは 値のオーバーフローを避けるため & inv[i] は常にm modVal を前提とした計算で 掛け算に用いられるため どの段階で %modVal をとっても影響は無い

        inv[i] = modVal - inv[(modVal % i).toInt()] * (modVal / i) % modVal

        // inv[i] の結果を finv[i-1] に掛け合わせて mod を取った結果が finv[i] ( 逆元の累乗 ) 累乗の逆元 = 逆元の総積
        finv[i] = finv[i - 1] * inv[i] % modVal
    }
}


/**
 * 事前に計算した逆元テーブルから nCk mod modVal を計算
 */
fun combinationModWithTable(n: Long, k: Long, modVal: Long): Long {
    val numeTerm = fact[n.toInt()]
    val denomTerm = finv[k.toInt()] * finv[(n - k).toInt()] % modVal

    return (numeTerm * denomTerm % modVal)
}


/**
 * nCk mod modVal を計算する
 *
 * 分子部分は O(N) で計算
 * 分母を考える際に 割り算に対する mod を定義する必要がある
 */
@Deprecated(message = "This Implementation might be wrong. Use combinationModWithTable")
fun combinationMod(n: Long, k: Long, modVal: Long): Long {

    // O(N)
    val nume = factMod(n, modVal)

    // これは実は間違っている
    // 累乗の modVal 後の 逆元 と　累乗の逆元は異なる 式的には
    // r! = q (mod p の場合) でも (r!)^-1 = q^-1 (mod p の場合） は成り立たないということ, いや成り立つかはわからないので使わないほうがbetter
    val denom = (factMod(k, modVal) * factMod(n - k, modVal)) % modVal

    // -> この方針だと OverFlow を避けるために， k! の逆元を求めるために 1 ~ k の各値の逆元を計算しなくてはいけない O(k log K?) は必要なので辛い?

    return modDiv(nume, denom, modVal)
}

private fun factMod(n: Long, modVal: Long): Long {
    var ans = 1L
    for (i in 1..n) {
        ans = (ans * i) % modVal
    }

    return ans
}


fun modTimes(inputs: List<Long>, modVal: Long): Long {
    return inputs.reduce { acc, i -> (acc % modVal) * (i % modVal) % modVal }
}

/**
 * mod の世界での割り算を実装
 *
 * a/b mod p を求めたい場合 mod p における b の逆元(b^-1)を求めて (a * b^-1) mod p とする
 */

fun modDiv(numerator: Long, denominator: Long, modVal: Long): Long {

    // 拡張Euclidの互除法を用いた 逆元計算の実装では 負の 値が返ってくることもあるので (  + modVal) % modVal をする
    val invMod = (invModEuclid(denominator, modVal).first + modVal) % modVal

    return (numerator * invMod) % modVal
}

/**
 * mod modVal の世界での逆元を求める O(log modVal)
 *
 * 定義 a * b mod modVal = 1 となるとき b を mod modVal の世界での逆元と呼ぶ
 *
 * -> x / a mod modVal = x * b mod modVal と書き換える事ができる
 * -> mod の掛け算は逐次計算できるので OverFlow を避けられる （嬉しい）
 *
 * フェルマーの小定理を元にした実装, modVal は素数である必要がある
 */
fun invModFermat(a: Long, modVal: Long): Long {
    return powMod(a, modVal - 2, modVal)
}


/**
 * mod b の世界での a の逆元を求める
 * ユークリッドの互除法に類似した計算を行うので 最悪 O(log modVal) (ユークリッドの互除法の計算量は log modVal 以下になる)
 * -> 平均的には フェルマーの小定理を用いた逆元計算よりも計算量が少なくなる (あちらは 累乗計算で 必ず log(modVal-2) 必要
 *
 * a と modVal は互いに素 である必要がある
 *  <-> a , modVal が互いに素 でないとしたら？
 *
 *  ax + by = c を満たす整数 x, y が存在する必要十分条件は c = k gcd(a,b) を満たす k が存在すること (そうでなければ整数解は存在しない）
 *
 *  ->
 *
 *  ax + by = gcd(a,b) を満たす x, y は存在する
 *
 *  ->
 *
 *  a, b が互いに素 であれば
 *
 *  ax + by = 1 を満たす x, y は存在する といえる
 *
 *  ->
 *
 *  つまり逆元が存在する -> ユークリッドの互除法で解を求める
 *
 *  ※ 「mod p において a の逆元が存在する」 と 「ax + py = 1 となる 整数解 x,y が存在する」 は等価なので
 *     「mod p において a の逆元が存在する」 ための必要十分条件は 「p と a が互いに素」
 *
 *  丁寧に再帰計算で実装する,, アドレスを渡して，グローバル変数のようにみなして計算するのは賢すぎるので変数で渡していくことを考える
 *  -> ２つの値を受け取って２つの値を返す実装に
 *
 *  なお，最終的に用いる値は 返された値の第一要素なので注意 (使いづらい？）また，負の値が返ってくるかもしれないことには注意
 */
fun invModEuclid(a: Long, b: Long): Pair<Long, Long> {

    // 互除法同様, 渡されたあまりが 0 になったら再帰を終了する
    if (b == 0L) {
        return Pair(1, 0)
    }

    // １つ下の b, a%b に対する計算結果から a, b についての結果を計算する
    val (s, t) = invModEuclid(b, a % b)

    return Pair(t, s - a / b * t)
}

/**
 * a^n mod modVal を計算する
 * 二分累乗法で累乗計算を O(logN) で行いつつ，OverFlow を避けるため適宜 mod 計算を行う
 */
fun powMod(a: Long, n: Long, modVal: Long, ans: Long = 1): Long {

    if (n == 0L) {
        return ans
    }

    // ビット演算の結果 2^0 の桁が 0 の場合は ans に掛けずに次の桁へ
    if (n.and(1L) == 0L) {
        return powMod(a * a % modVal, n.shr(1), modVal, ans)
    }

    // ビット演算の結果 2^9 の毛型 1 の場合は ans を更新
    return powMod(a * a % modVal, n.shr(1), modVal, ans * a % modVal)
}


/**
 * 二分累乗法の実装 a^N を O(logN) で計算
 * 指数部分を２進数で考える -> 指数が2^n になっていなくても問題ない
 */
private fun pow(a: Long, n: Long, ans: Long = 1): Long {

    if (n == 0L) {
        return ans
    }

    // ビット演算の結果 2^0 の桁が 0 の場合は ans に掛けずに次の桁へ
    if (n.and(1L) == 0L) {
        return pow(a * a, n.shr(1), ans)
    }

    // ビット演算の結果 2^9 の毛型 1 の場合は ans を更新
    return pow(a * a, n.shr(1), ans * a)
}