NOL_LESS - Editorial

PROBLEM LINK:

Contest Division 1
Contest Division 2
Contest Division 3
Practice

Setter: Alex Kristev
Tester: Nishank Suresh
Editorialist: Taranpreet Singh

DIFFICULTY

Medium

PREREQUISITES

Dynamic Programming, Number Theoretic Transform

PROBLEM

You are trying to create an array A containing N integers. However, your array-creating-machine has some restrictions. More formally, you are given a constant D, and two arrays B and C, each with M elements, then:

  • A_i \le D for all 1 \le i \le N
  • A_i \ge A_{i - 1} for all 1 \lt i \le N
  • A_{C_j}\cdot B_j \le D for all 1 \le j \le M

Find how many different arrays can you create. Since the answer can be big, print it modulo
998244353.

QUICK EXPLANATION

  • Based on restrictions, we can determine the maximum value we can assign for each position.
  • This way, we can write a DP defined by state (n, x) denote the number of non-decreasing arrays of length n such that the last element is x. We have transitions \displaystyle DP_{n, x} = \sum_{1 \leq y \leq x} DP_{n-1, y}, and base state DP_{0, 1} = 1. This approach takes O(N*D^2)
  • In order to optimize this, we can notice that there can be at most M intervals where the maximum value is the same, so we can group those transitions by some combinatorics, reducing complexity to O(M*D^2)
  • The sum of distinct values of D/i is bounded by D*log(D), which reduces the time complexity to O(D^2*log(D))
  • By replacing DP transitions with polynomial convolution, the time complexity is reduced to O(D*log^2(D)) which should get accepted with a reasonable constant factor.

EXPLANATION

Let’s say we have a restriction (C_i, B_i) where A_{C_i}*B_i \leq D. This implies A_{C_i} \leq \lfloor D/B_i \rfloor. We also have A_{i-1} \leq A_i, so a restriction affecting position p affects all positions before p as well.

This way, we can compute for each position, the maximum value allowed for that position.

This allows us to write a naive Dynamic Programming approach, where we define state (n, x) to be the number of arrays of first n elements such that A_n = x. Let’s say the maximum value allowed for index p is MX_p. So we can write \displaystyle DP_{n, x} = \sum_{1 \leq y \leq x} DP_{n-1, y}. This solution naviely takes O(N*D^2), and can be sped up to O(N*D) using prefix sums. But we need to improve it a lot.

Observation: Since there are M restrictions, the number of distinct values of MX_p doesn’t exceed M. Now, we can divide all positions in the range [1, N] into M+1 groups.

Let’s say we have A_p = x and A_q = y where p \lt q and x \leq y. What is the number of ways to choose the values for positions in range (p, q) such that array is non-decreasing? Let’s denote A = q-p-1 and V = y-x

Using Stars and Bars, we can find it out to be ^{q-p-1+y-x}C_{y-x}. Hence, we can now process the whole group having the same maximum in the same way. Just adding a restriction with C_i = N and B_i = 1 to avoid handling edge cases for last block.

The DP transitions now become (m, x) denote the number of ways to fill first m groups such that last position in last group has value x. Each group contains a number of people and a next maximum value. DP_{m, x} = \sum_{y \leq x} DP_{m-1, y} * ^{S_m-1+y-x}C_{S_m-1}, where S_m denote the number of position in m-th group.

This approach requires M groups and each group involves O(D^2) transitions, leading to O(M*D^2) solution. This solution can be further optimized by replacing the transition with polynomial multiplication. Let’s say for m-th group, MX_m denote the maximum value. Assuming the coefficient of x^v denote the number of ways such that the last element has value v, then we need to take the whole polynomial modulo x^{MX_m+1}. This would get rid of coefficients of degree \gt MX_m.

Hence, by replacing transitions with NTT, we get the time complexity down to O(M*D*log(D)), which should easily pass the first subtask.

Surprise: Above solution’s time complexity is actually O(D*log^2(D)) and would pass both subtasks.

Why? The convolution needs to happen whenever the maximum of a group changes. The way restrictions are given, the maximum value can only take values of the form D/x for some x, and there are around \sqrt D distinct values of D/x for fixed x. Moreover, the sum of D/x over all such instances would be approximately D*log(D), reducing the bound on overall time complexity to O(D*log^2(D)). Also, we need to compute factorials and inverse factorials to compute C, so we have another O(N) in time complexity.

TIME COMPLEXITY

The time complexity is O(N + \sum D*log^2(D)) per test case.

SOLUTIONS

Setter's Solution
#include <bits/stdc++.h>
using namespace std;

#define FAST ios_base::sync_with_stdio(false); \
             cin.tie(nullptr);                 \
             cout.tie(nullptr)

using ll = long long;
using ld = long double;
using uint = unsigned int;
using ull = unsigned long long;
template<typename T>
using pair2 = pair<T, T>;
using pii = pair<int, int>;
using pli = pair<ll, int>;
using pll = pair<ll, ll>;
using vi = vector<int>;
using vl = vector<ll>;
using vvi = vector<vector<int>>;
using vvl = vector<vector<ll>>;
using vpii = vector<pii>;
using vpll = vector<pll>;

#define pb push_back
#define mp make_pair
#define all(x) (x).begin(),(x).end()
#define psum(x) ((x).first + (x).second)
#define ft first
#define sd second
#define cendl cout << endl
#define cyes cout << "YES" << endl
#define cno cout << "NO" << endl

template<int m>
struct modint
{
    int x;

    modint() :
        x(0) {}

    modint(long long arg)
    {
        arg%= m;
        if (arg < 0) x = arg + m;
        else x = arg;
    }

    modint& operator+= (const modint& other)
    {
        x += other.x;
        if (x >= m) x -= m;

        return *this;
    }

    modint& operator*= (const modint& other)
    {
        x = (x * 1ll * other.x) % m;
        return *this;
    }

    modint& operator-= (const modint& other)
    {
        x+= m - other.x;
        if (x >= m) x-= m;

        return *this;
    }

    modint operator+ (const modint& other) const
    {
        modint tmp = *this;
        tmp += other;
        return tmp;
    }

    modint operator- (const modint& other) const
    {
        modint tmp = *this;
        tmp -= other;
        return tmp;
    }

    modint operator* (const modint& other) const
    {
        modint tmp = *this;
        tmp *= other;
        return tmp;
    }

    explicit operator int () const
    {
        return x;
    }

    modint& operator++ ()
    {
        ++x;
        if (x == m) x = 0;

        return *this;
    }

    modint& operator-- ()
    {
        if (x == 0) x = m-1;
        else --x;

        return *this;
    }

    modint operator++ (int)
    {
        modint tmp = *this;
        ++*this;

        return tmp;
    }

    modint operator-- (int)
    {
        modint tmp = *this;
        --*this;

        return tmp;
    }

    bool operator== (const modint& other) const
    {
        return x == other.x;
    }

    bool operator!= (const modint& other) const
    {
        return x != other.x;
    }

    template<class T>
    modint operator^ (T arg) const
    {
        if (arg == 0) return 1;
        if (arg == 1) return x;

        auto t = *this ^ (arg >> 1);
        t*= t;

        if (arg & 1) t*= *this;

        return t;
    }

    modint inv() const // works clearly only when 'm' is prime number
    {
        return *this ^ (m-2);
    }
};

const int MOD = 998244353;
typedef modint<MOD> mint;

std::vector<mint> factorial;
std::vector<mint> invfactorial;

inline mint C(int N, int K)
{
    if (N < K || K < 0)
        return mint();

    return factorial[N] * invfactorial[K] * invfactorial[N-K];
}

inline void SetupFactorial(int sz)
{
    ++sz;
    ::factorial.resize(sz);
    ::invfactorial.resize(sz);

    ::factorial[0] = 1;
    for (int i = 1; i < sz; ++i)
        ::factorial[i] = ::factorial[i-1] * mint(i);

    ::invfactorial[sz-1] = ::factorial[sz-1].inv();
    for (int i = sz-2; i >= 0; --i)
        ::invfactorial[i] = ::invfactorial[i+1]*mint(i+1);
}

const int mod = MOD;
const int root = 31;
const int root_1 = 128805723;
const int root_pw = 1<<23;
const mint mroot = mint(root);
const mint mroot_1 = mint(root_1);

void fft (vector<mint>& a, bool invert)
{
    int n = (int) a.size();

    for (int i=1, j=0; i<n; ++i)
    {
	    int bit = n >> 1;
	    for (; j >= bit; bit >>= 1)
		    j -= bit;
	    j += bit;
	    if (i < j)
		    swap (a[i], a[j]);
    }

    for (int len = 2; len <= n; len <<= 1)
    {
	    mint wlen = invert ? mroot_1 : mroot;
	    for (int i = len; i < root_pw; i <<= 1)
		    wlen *= wlen;
	    for (int i = 0; i < n; i += len)
	    {
		    mint w(1);
		    for (int j = 0; j < len/2; ++j)
		    {
			    mint u = a[i+j];
			    mint v = a[i+j+len/2] * w;
			    a[i + j] = u + v;
			    a[i + j + len/2] = u - v;
			    w *= wlen;
		    }
	    }
    }

    if (invert)
    {
	    mint nrev = mint(n).inv();
	    for (int i = 0; i < n; ++i)
		    a[i] *= nrev;
    }
}

void multiply (const vector<mint>& a, const vector<mint>& b, size_t sz, size_t ressz, vector<mint>& res)
{
    size_t n = 1;
    while (n < sz)  n <<= 1;
    n <<= 1;

    vector<mint> fa (a.begin(), a.end()),  fb (b.begin(), b.end());
    fa.resize(n),  fb.resize(n);

    for (int i = sz; i < n; ++i)
    {
        fa[i] = mint();
        fb[i] = mint();
    }


    fft (fa, false),  fft (fb, false);
    for (size_t i=0; i<n; ++i)
	    fa[i] *= fb[i];
    fft (fa, true);

    n = min(n, ressz);
    if (res.size() < n)
        res.resize (n);
    for (size_t i=0; i<n; ++i)
	    res[i] = fa[i];
}


int a, b, k, n, m, tmp, d;



int solve()
{
    cin >> n >> m >> d;

    vpii data(m);

    for (int i = 0; i < m; ++i)
    {
        cin >> data[i].ft >> data[i].sd;
        data[i].sd = d / data[i].sd;
    }


    sort(all(data));

    if (data[data.size()-1].ft != n)
        data.pb({n, d});

    vpii dt;
    int cur_rest = d+1;

    for (int i = data.size()-1; i >= 0; --i)
    {
        if (data[i].sd < cur_rest)
        {
            cur_rest = data[i].sd;
            dt.pb(data[i]);
        }
    }

    sort(all(dt));


    vector<mint> dp (1, mint(1));
    vector<mint> dpnw (d+1);

    int curid = 0;
    int curmax = 1;
    for (int id = 0; id < dt.size(); ++id)
    {
        vector<mint> Cnk (dt[id].sd);
        for (int i = 0; i < dt[id].sd; ++i)
            Cnk[i] = C(i+dt[id].ft-curid-1, i);

        multiply(dp, Cnk, Cnk.size(), Cnk.size(), dp);

        curmax = dt[id].sd;
        curid = dt[id].ft;
    }

    mint ans(0);
    for (int i = 0; i < dp.size(); ++i)
        ans += dp[i];

    cout <<  ans.x << endl;

    return 0;
}


int main()
{
    FAST;

    SetupFactorial(11000004);

    int t;
    cin >> t;
    while(t--)
        solve();

    return 0;
}
Tester's Solution
#include "bits/stdc++.h"
// #pragma GCC optimize("O3,unroll-loops")
// #pragma GCC target("avx2,bmi,bmi2,lzcnt,popcnt")
using namespace std;
using ll = long long int;
mt19937_64 rng(chrono::high_resolution_clock::now().time_since_epoch().count());
 
/**
 * Integers modulo p, where p is a prime
 * Source: Aeren (modified from tourist?)
 *         Modmul for 64-bit mod from kactl:ModMulLL
 * Works with p < 7.2e18 with x87 80-bit long double, and p < 2^52 ~ 4.5e12 with 64-bit
 */
template<typename T>
struct Z_p{
    using Type = typename decay<decltype(T::value)>::type;
    static vector<Type> MOD_INV;
    constexpr Z_p(): value(){ }
    template<typename U> Z_p(const U &x){ value = normalize(x); }
    template<typename U> static Type normalize(const U &x){
        Type v;
        if(-mod() <= x && x < mod()) v = static_cast<Type>(x);
        else v = static_cast<Type>(x % mod());
        if(v < 0) v += mod();
        return v;
    }
    const Type& operator()() const{ return value; }
    template<typename U> explicit operator U() const{ return static_cast<U>(value); }
    constexpr static Type mod(){ return T::value; }
    Z_p &operator+=(const Z_p &otr){ if((value += otr.value) >= mod()) value -= mod(); return *this; }
    Z_p &operator-=(const Z_p &otr){ if((value -= otr.value) < 0) value += mod(); return *this; }
    template<typename U> Z_p &operator+=(const U &otr){ return *this += Z_p(otr); }
    template<typename U> Z_p &operator-=(const U &otr){ return *this -= Z_p(otr); }
    Z_p &operator++(){ return *this += 1; }
    Z_p &operator--(){ return *this -= 1; }
    Z_p operator++(int){ Z_p result(*this); *this += 1; return result; }
    Z_p operator--(int){ Z_p result(*this); *this -= 1; return result; }
    Z_p operator-() const{ return Z_p(-value); }
    template<typename U = T>
    typename enable_if<is_same<typename Z_p<U>::Type, int>::value, Z_p>::type &operator*=(const Z_p& rhs){
        #ifdef _WIN32
        uint64_t x = static_cast<int64_t>(value) * static_cast<int64_t>(rhs.value);
        uint32_t xh = static_cast<uint32_t>(x >> 32), xl = static_cast<uint32_t>(x), d, m;
        asm(
            "divl %4; \n\t"
            : "=a" (d), "=d" (m)
            : "d" (xh), "a" (xl), "r" (mod())
        );
        value = m;
        #else
        value = normalize(static_cast<int64_t>(value) * static_cast<int64_t>(rhs.value));
        #endif
        return *this;
    }
    template<typename U = T>
    typename enable_if<is_same<typename Z_p<U>::Type, int64_t>::value, Z_p>::type &operator*=(const Z_p &rhs){
        uint64_t ret = static_cast<uint64_t>(value) * static_cast<uint64_t>(rhs.value) - static_cast<uint64_t>(mod()) * static_cast<uint64_t>(1.L / static_cast<uint64_t>(mod()) * static_cast<uint64_t>(value) * static_cast<uint64_t>(rhs.value));
        value = normalize(static_cast<int64_t>(ret + static_cast<uint64_t>(mod()) * (ret < 0) - static_cast<uint64_t>(mod()) * (ret >= static_cast<uint64_t>(mod()))));
        return *this;
    }
    template<typename U = T>
    typename enable_if<!is_integral<typename Z_p<U>::Type>::value, Z_p>::type &operator*=(const Z_p &rhs){
        value = normalize(value * rhs.value);
        return *this;
    }
    template<typename U>
    Z_p &operator^=(U e){
        if(e < 0) *this = 1 / *this, e = -e;
        Z_p res = 1;
        for(; e; *this *= *this, e >>= 1) if(e & 1) res *= *this;
        return *this = res;
    }
    template<typename U>
    Z_p operator^(U e) const{
        return Z_p(*this) ^= e;
    }
    Z_p &operator/=(const Z_p &otr){
        Type a = otr.value, m = mod(), u = 0, v = 1;
        if(a < (int)MOD_INV.size()) return *this *= MOD_INV[a];
        while(a){
            Type t = m / a;
            m -= t * a; swap(a, m);
            u -= t * v; swap(u, v);
        }
        assert(m == 1);
        return *this *= u;
    }
    template<typename U> friend const Z_p<U> &abs(const Z_p<U> &v){ return v; }
    Type value;
};
template<typename T> bool operator==(const Z_p<T> &lhs, const Z_p<T> &rhs){ return lhs.value == rhs.value; }
template<typename T, typename U, typename enable_if<is_integral<U>::value>::type* = nullptr> bool operator==(const Z_p<T>& lhs, U rhs){ return lhs == Z_p<T>(rhs); }
template<typename T, typename U, typename enable_if<is_integral<U>::value>::type* = nullptr> bool operator==(U lhs, const Z_p<T> &rhs){ return Z_p<T>(lhs) == rhs; }
template<typename T> bool operator!=(const Z_p<T> &lhs, const Z_p<T> &rhs){ return !(lhs == rhs); }
template<typename T, typename U, typename enable_if<is_integral<U>::value>::type* = nullptr> bool operator!=(const Z_p<T> &lhs, U rhs){ return !(lhs == rhs); }
template<typename T, typename U, typename enable_if<is_integral<U>::value>::type* = nullptr> bool operator!=(U lhs, const Z_p<T> &rhs){ return !(lhs == rhs); }
template<typename T> bool operator<(const Z_p<T> &lhs, const Z_p<T> &rhs){ return lhs.value < rhs.value; }
template<typename T> bool operator>(const Z_p<T> &lhs, const Z_p<T> &rhs){ return lhs.value > rhs.value; }
template<typename T> bool operator<=(const Z_p<T> &lhs, const Z_p<T> &rhs){ return lhs.value <= rhs.value; }
template<typename T> bool operator>=(const Z_p<T> &lhs, const Z_p<T> &rhs){ return lhs.value >= rhs.value; }
template<typename T> Z_p<T> operator+(const Z_p<T> &lhs, const Z_p<T> &rhs){ return Z_p<T>(lhs) += rhs; }
template<typename T, typename U, typename enable_if<is_integral<U>::value>::type* = nullptr> Z_p<T> operator+(const Z_p<T> &lhs, U rhs){ return Z_p<T>(lhs) += rhs; }
template<typename T, typename U, typename enable_if<is_integral<U>::value>::type* = nullptr> Z_p<T> operator+(U lhs, const Z_p<T> &rhs){ return Z_p<T>(lhs) += rhs; }
template<typename T> Z_p<T> operator-(const Z_p<T> &lhs, const Z_p<T> &rhs){ return Z_p<T>(lhs) -= rhs; }
template<typename T, typename U, typename enable_if<is_integral<U>::value>::type* = nullptr> Z_p<T> operator-(const Z_p<T>& lhs, U rhs){ return Z_p<T>(lhs) -= rhs; }
template<typename T, typename U, typename enable_if<is_integral<U>::value>::type* = nullptr> Z_p<T> operator-(U lhs, const Z_p<T> &rhs){ return Z_p<T>(lhs) -= rhs; }
template<typename T> Z_p<T> operator*(const Z_p<T> &lhs, const Z_p<T> &rhs){ return Z_p<T>(lhs) *= rhs; }
template<typename T, typename U, typename enable_if<is_integral<U>::value>::type* = nullptr> Z_p<T> operator*(const Z_p<T>& lhs, U rhs){ return Z_p<T>(lhs) *= rhs; }
template<typename T, typename U, typename enable_if<is_integral<U>::value>::type* = nullptr> Z_p<T> operator*(U lhs, const Z_p<T> &rhs){ return Z_p<T>(lhs) *= rhs; }
template<typename T> Z_p<T> operator/(const Z_p<T> &lhs, const Z_p<T> &rhs) { return Z_p<T>(lhs) /= rhs; }
template<typename T, typename U, typename enable_if<is_integral<U>::value>::type* = nullptr> Z_p<T> operator/(const Z_p<T>& lhs, U rhs) { return Z_p<T>(lhs) /= rhs; }
template<typename T, typename U, typename enable_if<is_integral<U>::value>::type* = nullptr> Z_p<T> operator/(U lhs, const Z_p<T> &rhs) { return Z_p<T>(lhs) /= rhs; }
template<typename T> istream &operator>>(istream &in, Z_p<T> &number){
    typename common_type<typename Z_p<T>::Type, int64_t>::type x;
    in >> x;
    number.value = Z_p<T>::normalize(x);
    return in;
}
template<typename T> ostream &operator<<(ostream &out, const Z_p<T> &number){ return out << number(); }
 
/*
using ModType = int;
struct VarMod{ static ModType value; };
ModType VarMod::value;
ModType &mod = VarMod::value;
using Zp = Z_p<VarMod>;
*/
 
// constexpr int mod = 1e9 + 7; // 1000000007
constexpr int mod = (119 << 23) + 1; // 998244353
// constexpr int mod = 1e9 + 9; // 1000000009
using Zp = Z_p<integral_constant<decay<decltype(mod)>::type, mod>>;
 
template<typename T> vector<typename Z_p<T>::Type> Z_p<T>::MOD_INV;
template<typename T = integral_constant<decay<decltype(mod)>::type, mod>>
void precalc_inverse(int SZ){
    auto &inv = Z_p<T>::MOD_INV;
    if(inv.empty()) inv.assign(2, 1);
    for(; inv.size() <= SZ; ) inv.push_back((mod - 1LL * mod / (int)inv.size() * inv[mod % (int)inv.size()]) % mod);
}
 
template<typename T>
vector<T> precalc_power(T base, int SZ){
    vector<T> res(SZ + 1, 1);
    for(auto i = 1; i <= SZ; ++ i) res[i] = res[i - 1] * base;
    return res;
}
 
template<typename T>
vector<T> precalc_factorial(int SZ){
    vector<T> res(SZ + 1, 1); res[0] = 1;
    for(auto i = 1; i <= SZ; ++ i) res[i] = res[i - 1] * i;
    return res;
}
 
ll modpow(ll a, ll n) {
    a %= mod;
    ll r = 1;
    while (n) {
        if (n&1) r = (r*a)%mod;
        a = (a*a)%mod;
        n /= 2;
    }
    return r;
}
 
const int root = 62;
void ntt(vector<ll> &a) {
    int n = size(a), L = 31 - __builtin_clz(n);
    static vector<ll> rt(2, 1);
    for (static int k = 2, s = 2; k < n; k *= 2, s++) {
        rt.resize(n);
        ll z[] = {1, modpow(root, mod >> s)};
        for (int i = k; i < 2*k; ++i) rt[i] = rt[i / 2] * z[i & 1] % mod;
    }
    vector<int> rev(n);
    for (int i = 0; i < n; ++i) rev[i] = (rev[i / 2] | (i & 1) << L) / 2;
    for (int i = 0; i < n; ++i) if (i < rev[i]) swap(a[i], a[rev[i]]);
    for (int k = 1; k < n; k *= 2)
        for (int i = 0; i < n; i += 2 * k) for (int j = 0; j < k; ++j) {
            ll z = rt[j + k] * a[i + j + k] % mod, &ai = a[i + j];
            a[i + j + k] = ai - z + (z > ai ? mod : 0);
            ai += (ai + z >= mod ? z - mod : z);
        }
}
vector<ll> conv(const vector<ll> &a, const vector<ll> &b) {
    if (a.empty() || b.empty()) return {};
    int s = size(a) + size(b) - 1, B = 32 - __builtin_clz(s), n = 1 << B;
    int inv = modpow(n, mod - 2);
    vector<ll> L(a), R(b), out(n);
    L.resize(n), R.resize(n);
    ntt(L), ntt(R);
    for (int i = 0; i < n; ++i) out[-i & (n - 1)] = (ll)L[i] * R[i] % mod * inv % mod;
    ntt(out);
    return {out.begin(), out.begin() + s};
}
 
int main()
{
    ios::sync_with_stdio(0); cin.tie(0);
 
    const int MAXN = 1.1e7;
    auto fac = precalc_factorial<Zp>(MAXN);
    auto C = [&] (int n, int r) {
        if (r < 0 or r > n) return Zp(0);
        return fac[n] / (fac[r] * fac[n-r]);
    };
 
    int t; cin >> t;
    while (t--) {
        int n, m , d; cin >> n >> m >> d;
        
        vector<array<int, 2>> constraint(m);
        for (int i = 0; i < m; ++i) {
            int pos, val; cin >> pos >> val;
            constraint[i] = {pos, d/val};
        }
        sort(begin(constraint), end(constraint));
        if (constraint.back()[0] != n) constraint.push_back({n, d});
        vector<array<int, 2>> reduced;
        for (auto [pos, val] : constraint) {
            while (!reduced.empty()) {
                auto [x, y] = reduced.back();
                if (y < val) break;
                reduced.pop_back();
            }
            reduced.push_back({pos, val});
        }
        
        vector<ll> dp(2); dp[1] = 1;
        int prevval = 1, prevpos = 0;
        for (auto [pos, val] : reduced) {
            auto dp2 = dp;
            dp.resize(val+1);
 
            int len = pos - prevpos - 1;
            for (int i = 0; i <= val; ++i)
                dp[i] = C(len + i, i)();

            auto res = conv(dp, dp2);
            dp = res;
            while (dp.size() > val+1) dp.pop_back();
 
            prevpos = pos;
            prevval = val;
        }
        cout << accumulate(begin(dp), end(dp), Zp(0)) << '\n';
    }
}
Editorialist's Solution
import java.util.*;
import java.io.*;
class NOL_LESS{
    //SOLUTION BEGIN
    long[][] fif;
    long MOD = 998244353;
    long BIG = MOD*MOD;
    void pre() throws Exception{
        fif = fif(100000+10000000);
    }
    void solve(int TC) throws Exception{
        int N = ni(), M = ni(), D = ni();
        int[][] restr = new int[M][];
        for(int i = 0; i< M; i++)restr[i] = new int[]{ni()-1, ni()};
        Arrays.sort(restr, (int[] i1, int[] i2) -> Integer.compare(i1[0], i2[0]));
        int[][] intervals = new int[M+1][];
        int cnt = 0;
        int last = N-1, maxVal = D;
        for(int i = M-1; i>= 0; i--){
            if(D/restr[i][1] < maxVal){
                if(last > restr[i][0])intervals[cnt++] = new int[]{restr[i][0]+1, last, maxVal};
                maxVal = D/restr[i][1];
                last = restr[i][0];
            }
        }
        intervals[cnt++] = new int[]{0, last, maxVal};
        for(int l = 0, r = cnt-1; l< r; l++, r--){
            int[] tmp = intervals[l];
            intervals[l] = intervals[r];
            intervals[r] = tmp;
        }

        long[] ways = new long[]{0, 1};
        for(int i = 0; i< cnt; i++){
            int C = intervals[i][1]-intervals[i][0]+1, upto = intervals[i][2];
            long[] P = new long[1+upto];
            for(int j = 0; j <= upto; j++)P[j] = C(j+C-1, C-1);
            ways = Arrays.copyOf(Convolution.convolution(ways, P, (int)MOD), 1+upto);
        }
        long ans = 0;
        for(long x:ways)ans = (ans+x)%MOD;
        pn(ans);
    }
    void dbg(Object... o){System.out.println(Arrays.deepToString(o));}
    long ways(int min, int max, int C){
        int M = max-min;
        return C(M+C-1, M);
    }
    long[][] fif(int mx){
        mx++;
        long[] F = new long[mx], IF = new long[mx];
        F[0] = 1;
        for(int i = 1; i< mx; i++)F[i] = (F[i-1]*i)%MOD;
        //GFG
        long M = MOD; 
        long y = 0, x = 1; 
        long a = F[mx-1];
        while(a> 1){ 
            long q = a/M;
            long t = M; 
            M = a%M;
            a = t;
            t = y;
            y = x-q*y;
            x = t;
        } 
        if(x<0)x+=MOD;
        IF[mx-1] = x;
        for(int i = mx-2; i>= 0; i--)IF[i] = (IF[i+1]*(i+1))%MOD;
        return new long[][]{F, IF};
    }
    long C(int n, int r){
        if(n<r || r<0)return 0;
        return (fif[0][n]*((fif[1][r]*fif[1][n-r])%MOD))%MOD;
    }
    long inv(long x){
        long o = 1;
        for(long p = MOD-2; p > 0; p>>=1){
            if((p&1)==1)o = (o*x)%MOD;
            x = (x*x)%MOD;
        }
        return o;
    }
    /**
    * Convolution.
    *
    * @verified https://atcoder.jp/contests/practice2/submissions/24449847
    * @verified https://judge.yosupo.jp/submission/53841
    */
   static class Convolution {
       /**
        * Find a primitive root.
        *
        * @param m A prime number.
        * @return Primitive root.
        */
       private static int primitiveRoot(int m) {
           if (m == 2) return 1;
           if (m == 167772161) return 3;
           if (m == 469762049) return 3;
           if (m == 754974721) return 11;
           if (m == 998244353) return 3;

           int[] divs = new int[20];
           divs[0] = 2;
           int cnt = 1;
           int x = (m - 1) / 2;
           while (x % 2 == 0) x /= 2;
           for (int i = 3; (long) (i) * i <= x; i += 2) {
               if (x % i == 0) {
                   divs[cnt++] = i;
                   while (x % i == 0) {
                       x /= i;
                   }
               }
           }
           if (x > 1) {
               divs[cnt++] = x;
           }
           for (int g = 2; ; g++) {
               boolean ok = true;
               for (int i = 0; i < cnt; i++) {
                   if (pow(g, (m - 1) / divs[i], m) == 1) {
                       ok = false;
                       break;
                   }
               }
               if (ok) return g;
           }
       }

       /**
        * Power.
        *
        * @param x Parameter x.
        * @param n Parameter n.
        * @param m Mod.
        * @return n-th power of x mod m.
        */
       private static long pow(long x, long n, int m) {
           if (m == 1) return 0;
           long r = 1;
           long y = x % m;
           while (n > 0) {
               if ((n & 1) != 0) r = (r * y) % m;
               y = (y * y) % m;
               n >>= 1;
           }
           return r;
       }

       /**
        * Ceil of power 2.
        *
        * @param n Value.
        * @return Ceil of power 2.
        */
       private static int ceilPow2(int n) {
           int x = 0;
           while ((1L << x) < n) x++;
           return x;
       }

       private static class FftInfo {
           private static int bsfConstexpr(int n) {
               int x = 0;
               while ((n & (1 << x)) == 0) x++;
               return x;
           }

           private static long inv(long a, long mod) {
               long b = mod;
               long p = 1, q = 0;
               while (b > 0) {
                   long c = a / b;
                   long d;
                   d = a;
                   a = b;
                   b = d % b;
                   d = p;
                   p = q;
                   q = d - c * q;
               }
               return p < 0 ? p + mod : p;
           }

           private final int rank2;
           public final long[] root;
           public final long[] iroot;
           public final long[] rate2;
           public final long[] irate2;
           public final long[] rate3;
           public final long[] irate3;

           public FftInfo(int g, int mod) {
               rank2 = bsfConstexpr(mod - 1);
               root = new long[rank2 + 1];
               iroot = new long[rank2 + 1];
               rate2 = new long[Math.max(0, rank2 - 2 + 1)];
               irate2 = new long[Math.max(0, rank2 - 2 + 1)];
               rate3 = new long[Math.max(0, rank2 - 3 + 1)];
               irate3 = new long[Math.max(0, rank2 - 3 + 1)];

               root[rank2] = pow(g, (mod - 1) >> rank2, mod);
               iroot[rank2] = inv(root[rank2], mod);
               for (int i = rank2 - 1; i >= 0; i--) {
                   root[i] = root[i + 1] * root[i + 1] % mod;
                   iroot[i] = iroot[i + 1] * iroot[i + 1] % mod;
               }

               {
                   long prod = 1, iprod = 1;
                   for (int i = 0; i <= rank2 - 2; i++) {
                       rate2[i] = root[i + 2] * prod % mod;
                       irate2[i] = iroot[i + 2] * iprod % mod;
                       prod = prod * iroot[i + 2] % mod;
                       iprod = iprod * root[i + 2] % mod;
                   }
               }
               {
                   long prod = 1, iprod = 1;
                   for (int i = 0; i <= rank2 - 3; i++) {
                       rate3[i] = root[i + 3] * prod % mod;
                       irate3[i] = iroot[i + 3] * iprod % mod;
                       prod = prod * iroot[i + 3] % mod;
                       iprod = iprod * root[i + 3] % mod;
                   }
               }
           }
       };

       /**
        * Garner's algorithm.
        *
        * @param c    Mod convolution results.
        * @param mods Mods.
        * @return Result.
        */
       private static long garner(long[] c, int[] mods) {
           int n = c.length + 1;
           long[] cnst = new long[n];
           long[] coef = new long[n];
           java.util.Arrays.fill(coef, 1);
           for (int i = 0; i < n - 1; i++) {
               int m1 = mods[i];
               long v = (c[i] - cnst[i] + m1) % m1;
               v = v * pow(coef[i], m1 - 2, m1) % m1;

               for (int j = i + 1; j < n; j++) {
                   long m2 = mods[j];
                   cnst[j] = (cnst[j] + coef[j] * v) % m2;
                   coef[j] = (coef[j] * m1) % m2;
               }
           }
           return cnst[n - 1];
       }

       /**
        * Inverse NTT.
        *
        * @param a     Target array.
        * @param g    Primitive root of mod.
        * @param mod   NTT Prime.
        */
       private static void butterflyInv(long[] a, int g, int mod) {
           int n = a.length;
           int h = ceilPow2(n);

           FftInfo info = new FftInfo(g, mod);

           int len = h;  // a[i, i+(n>>len), i+2*(n>>len), ..] is transformed
           while (len > 0) {
               if (len == 1) {
                   int p = 1 << (h - len);
                   long irot = 1;
                   for (int s = 0; s < (1 << (len - 1)); s++) {
                       int offset = s << (h - len + 1);
                       for (int i = 0; i < p; i++) {
                           long l = a[i + offset];
                           long r = a[i + offset + p];
                           a[i + offset] = (l + r) % mod;
                           a[i + offset + p] = (mod + l - r) % mod * irot % mod;
                       }
                       if (s + 1 != (1 << (len - 1))) {
                           irot *= info.irate2[Integer.numberOfTrailingZeros(~s)];
                           irot %= mod;
                       }
                   }
                   len--;
               } else {
                   // 4-base
                   int p = 1 << (h - len);
                   long irot = 1, iimag = info.iroot[2];
                   for (int s = 0; s < (1 << (len - 2)); s++) {
                       long irot2 = irot * irot % mod;
                       long irot3 = irot2 * irot % mod;
                       int offset = s << (h - len + 2);
                       for (int i = 0; i < p; i++) {
                           long a0 = 1L * a[i + offset + 0 * p];
                           long a1 = 1L * a[i + offset + 1 * p];
                           long a2 = 1L * a[i + offset + 2 * p];
                           long a3 = 1L * a[i + offset + 3 * p];

                           long a2na3iimag = 1L * (mod + a2 - a3) % mod * iimag % mod;

                           a[i + offset] = (a0 + a1 + a2 + a3) % mod;
                           a[i + offset + 1 * p] = (a0 + (mod - a1) + a2na3iimag) % mod * irot % mod;
                           a[i + offset + 2 * p] = (a0 + a1 + (mod - a2) + (mod - a3)) % mod * irot2 % mod;
                           a[i + offset + 3 * p] = (a0 + (mod - a1) + (mod - a2na3iimag)) % mod * irot3 % mod;
                       }
                       if (s + 1 != (1 << (len - 2))) {
                           irot *= info.irate3[Integer.numberOfTrailingZeros(~s)];
                           irot %= mod;
                       }
                   }
                   len -= 2;
               }
           }
       }

       /**
        * Inverse NTT.
        *
        * @param a    Target array.
        * @param g    Primitive root of mod.
        * @param mod  NTT Prime.
        */
       private static void butterfly(long[] a, int g, int mod) {
           int n = a.length;
           int h = ceilPow2(n);

           FftInfo info = new FftInfo(g, mod);

           int len = 0;  // a[i, i+(n>>len), i+2*(n>>len), ..] is transformed
           while (len < h) {
               if (h - len == 1) {
                   int p = 1 << (h - len - 1);
                   long rot = 1;
                   for (int s = 0; s < (1 << len); s++) {
                       int offset = s << (h - len);
                       for (int i = 0; i < p; i++) {
                           long l = a[i + offset];
                           long r = a[i + offset + p] * rot % mod;
                           a[i + offset] = (l + r) % mod;
                           a[i + offset + p] = (l + mod - r) % mod;
                       }
                       if (s + 1 != (1 << len)) {
                           rot *= info.rate2[Integer.numberOfTrailingZeros(~s)];
                           rot %= mod;
                       }
                   }
                   len++;
               } else {
                   // 4-base
                   int p = 1 << (h - len - 2);
                   long rot = 1, imag = info.root[2];
                   for (int s = 0; s < (1 << len); s++) {
                       long rot2 = rot * rot % mod;
                       long rot3 = rot2 * rot % mod;
                       int offset = s << (h - len);
                       for (int i = 0; i < p; i++) {
                           long mod2 = 1L * mod * mod;
                           long a0 = 1L * a[i + offset];
                           long a1 = 1L * a[i + offset + p] * rot % mod;
                           long a2 = 1L * a[i + offset + 2 * p] * rot2 % mod;
                           long a3 = 1L * a[i + offset + 3 * p] * rot3 % mod;
                           long a1na3imag = 1L * (a1 + mod2 - a3) % mod * imag % mod;
                           long na2 = mod2 - a2;
                           a[i + offset] = (a0 + a2 + a1 + a3) % mod;
                           a[i + offset + 1 * p] = (a0 + a2 + (2 * mod2 - (a1 + a3))) % mod;
                           a[i + offset + 2 * p] = (a0 + na2 + a1na3imag) % mod;
                           a[i + offset + 3 * p] = (a0 + na2 + (mod2 - a1na3imag)) % mod;
                       }
                       if (s + 1 != (1 << len)) {
                           rot *= info.rate3[Integer.numberOfTrailingZeros(~s)];
                           rot %= mod;
                       }
                   }
                   len += 2;
               }
           }
       }

       /**
        * Convolution.
        *
        * @param a   Target array 1.
        * @param b   Target array 2.
        * @param mod NTT Prime.
        * @return Answer.
        */
       public static long[] convolution(long[] a, long[] b, int mod) {
           int n = a.length;
           int m = b.length;
           if (n == 0 || m == 0) return new long[0];

           int z = 1 << ceilPow2(n + m - 1);
           {
               long[] na = new long[z];
               long[] nb = new long[z];
               System.arraycopy(a, 0, na, 0, n);
               System.arraycopy(b, 0, nb, 0, m);
               a = na;
               b = nb;
           }

           int g = primitiveRoot(mod);

           butterfly(a, g, mod);
           butterfly(b, g, mod);
           for (int i = 0; i < z; i++) {
               a[i] = a[i] * b[i] % mod;
           }
           butterflyInv(a, g, mod);
           a = java.util.Arrays.copyOf(a, n + m - 1);

           long iz = pow(z, mod - 2, mod);
           for (int i = 0; i < n + m - 1; i++) a[i] = a[i] * iz % mod;
           return a;
       }

       /**
        * Convolution.
        *
        * @param a   Target array 1.
        * @param b   Target array 2.
        * @param mod Any mod.
        * @return Answer.
        */
       public static long[] convolutionLL(long[] a, long[] b, int mod) {
           int n = a.length;
           int m = b.length;
           if (n == 0 || m == 0) return new long[0];

           int mod1 = 754974721;
           int mod2 = 167772161;
           int mod3 = 469762049;

           long[] c1 = convolution(a, b, mod1);
           long[] c2 = convolution(a, b, mod2);
           long[] c3 = convolution(a, b, mod3);

           int retSize = c1.length;
           long[] ret = new long[retSize];
           int[] mods = {mod1, mod2, mod3, mod};
           for (int i = 0; i < retSize; ++i) {
               ret[i] = garner(new long[]{c1[i], c2[i], c3[i]}, mods);
           }
           return ret;
       }

       /**
        * Naive convolution. (Complexity is O(N^2)!!)
        *
        * @param a   Target array 1.
        * @param b   Target array 2.
        * @param mod Mod.
        * @return Answer.
        */
       public static long[] convolutionNaive(long[] a, long[] b, int mod) {
           int n = a.length;
           int m = b.length;
           int k = n + m - 1;
           long[] ret = new long[k];
           for (int i = 0; i < n; i++) {
               for (int j = 0; j < m; j++) {
                   ret[i + j] += a[i] * b[j] % mod;
                   ret[i + j] %= mod;
               }
           }
           return ret;
       }
   }
    //SOLUTION END
    void hold(boolean b)throws Exception{if(!b)throw new Exception("Hold right there, Sparky!");}
    static boolean multipleTC = true;
    FastReader in;PrintWriter out;
    void run() throws Exception{
        in = new FastReader();
        out = new PrintWriter(System.out);
        //Solution Credits: Taranpreet Singh
        int T = (multipleTC)?ni():1;
        pre();for(int t = 1; t<= T; t++)solve(t);
        out.flush();
        out.close();
    }
    public static void main(String[] args) throws Exception{
        new NOL_LESS().run();
    }
    int bit(long n){return (n==0)?0:(1+bit(n&(n-1)));}
    void p(Object o){out.print(o);}
    void pn(Object o){out.println(o);}
    void pni(Object o){out.println(o);out.flush();}
    String n()throws Exception{return in.next();}
    String nln()throws Exception{return in.nextLine();}
    int ni()throws Exception{return Integer.parseInt(in.next());}
    long nl()throws Exception{return Long.parseLong(in.next());}
    double nd()throws Exception{return Double.parseDouble(in.next());}

    class FastReader{
        BufferedReader br;
        StringTokenizer st;
        public FastReader(){
            br = new BufferedReader(new InputStreamReader(System.in));
        }

        public FastReader(String s) throws Exception{
            br = new BufferedReader(new FileReader(s));
        }

        String next() throws Exception{
            while (st == null || !st.hasMoreElements()){
                try{
                    st = new StringTokenizer(br.readLine());
                }catch (IOException  e){
                    throw new Exception(e.toString());
                }
            }
            return st.nextToken();
        }

        String nextLine() throws Exception{
            String str = "";
            try{   
                str = br.readLine();
            }catch (IOException e){
                throw new Exception(e.toString());
            }  
            return str;
        }
    }
}

Feel free to share your approach. Suggestions are welcomed as always. :slight_smile:

This problem is toooo easy for D1 5th problem I guess.

bruh