// Original C++ solution (slower):
//

//  main.cpp

//



#include <algorithm>

#include <array>

#include <assert.h>

#include <iomanip>

#include <iostream>

#include <limits>

#include <math.h>

#include <memory>

#include <memory>

#include <queue>

#include <random>

#include <set>

#include <stdio.h>

#include <stdlib.h>

#include <string.h>

#include <string>

#include <tuple>

#include <unordered_map>

#include <unordered_set>

#include <vector>



using namespace std;



using ll = int64_t;

using ull = uint64_t;



template<typename T>

vector<T> make_vec_nd(T init, size_t size) {

    return vector<T>(size, init);

}

template<typename T, typename... Args>

auto make_vec_nd(T init, size_t size, Args... rest) {

    auto inner = make_vec_nd(init, rest...);

    return vector<decltype(inner)>(size, inner);

}



#define rep(i,a,b) for(ll i=(a);i<(b);i++)

#define rrep(i,a,b) for(ll i=(a)-1;i>=(b);i--)



int main() {

    ll N, W;

    cin >> N >> W;

    struct Item {

        ll w;

        ll v;

    };

    vector<Item> items(N);

    rep(i,0,N) {

        cin >> items[i].w >> items[i].v;

    }

    ll w_base = items[0].w;

    auto dp = make_vec_nd(numeric_limits<ll>::min(), 2, N+1, 300*N+1);

    dp[0][0][0] = 0;

    rep(n,1,N+1) {

        rep(m,0,n+1) {

            rep(w,0,300*N+1) {

                if (!(m * w_base + w <= W)) {

                    continue;

                }

                dp[n%2][m][w] = dp[(n-1)%2][m][w];

                ll w_off = items[n-1].w - items[0].w;

                if (m >= 1 && w >= w_off) {

                    dp[n%2][m][w] = max(dp[n%2][m][w], dp[(n-1)%2][m-1][w-w_off] + items[n-1].v);

                }

            }

        }

    }

    ll ans = numeric_limits<ll>::min();

    rep(m,0,N+1) {

        rep(w,0,300*N+1) {

            ans = max(ans, dp[N%2][m][w]);

        }

    }

    cout << ans << endl;

}




// Optimized solution to the same problem (faster):
//

//  main.cpp

//



#include <algorithm>

#include <array>

#include <assert.h>

#include <iomanip>

#include <iostream>

#include <limits>

#include <math.h>

#include <memory>

#include <memory>

#include <queue>

#include <random>

#include <set>

#include <stdio.h>

#include <stdlib.h>

#include <string.h>

#include <string>

#include <tuple>

#include <unordered_map>

#include <unordered_set>

#include <vector>



using namespace std;



using ll = int64_t;

using ull = uint64_t;



template<typename T>

vector<T> make_vec_nd(T init, size_t size) {

    return vector<T>(size, init);

}

template<typename T, typename... Args>

auto make_vec_nd(T init, size_t size, Args... rest) {

    auto inner = make_vec_nd(init, rest...);

    return vector<decltype(inner)>(size, inner);

}



#define rep(i,a,b) for(ll i=(a);i<(b);i++)

#define rrep(i,a,b) for(ll i=(a)-1;i>=(b);i--)



int main() {

    ll N, W;

    cin >> N >> W;

    struct Item {

        ll w;

        ll v;

    };

    vector<Item> items(N);

    rep(i,0,N) {

        cin >> items[i].w >> items[i].v;

    }

    ll w_base = items[0].w;

    auto dp = make_vec_nd(numeric_limits<ll>::min(), 2, N+1, 3*N+1);

    dp[0][0][0] = 0;

    rep(n,1,N+1) {

        rep(m,0,n+1) {

            rep(w,0,3*N+1) {

                if (!(m * w_base + w <= W)) {

                    continue;

                }

                dp[n%2][m][w] = dp[(n-1)%2][m][w];

                ll w_off = items[n-1].w - items[0].w;

                if (m >= 1 && w >= w_off) {

                    dp[n%2][m][w] = max(dp[n%2][m][w], dp[(n-1)%2][m-1][w-w_off] + items[n-1].v);

                }

            }

        }

    }

    ll ans = numeric_limits<ll>::min();

    rep(m,0,N+1) {

        rep(w,0,3*N+1) {

            ans = max(ans, dp[N%2][m][w]);

        }

    }

    cout << ans << endl;

}




// Fastest known solution for this problem: