#include <bits/stdc++.h>
using namespace std;
using int64 = long long;
const long long NEG = (long long)-4e18;
static inline long long ceil_div_pos(long long a, long long r){
// ceil(a/r) but clamp to >=0
if (a <= 0) return 0;
return (a + r - 1) / r;
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int N; long long W;
if (!(cin >> N >> W)) return 0;
// Gom theo weight
unordered_map<long long, vector<long long>> mp;
mp.reserve(N * 2);
for (int i = 0; i < N; ++i) {
long long v, w; cin >> v >> w;
mp[w].push_back(v);
}
// Danh sách trọng lượng phân biệt
vector<long long> b;
b.reserve(mp.size());
for (auto &kv : mp) b.push_back(kv.first);
sort(b.begin(), b.end());
int m = (int)b.size();
// Giá trị theo nhóm (giảm dần) và prefix
vector<vector<long long>> vals(m);
vector<int> cnt(m);
for (int i = 0; i < m; ++i) {
auto &v = mp[b[i]];
sort(v.begin(), v.end(), greater<long long>());
vals[i] = move(v);
cnt[i] = (int)vals[i].size();
}
// L_k, r_k, Delta_k
vector<long long> L(m);
for (int i = 0; i < m; ++i) L[i] = W / b[i];
vector<long long> r(m-1), Delta(m-1);
for (int i = 0; i + 1 < m; ++i) {
r[i] = b[i+1] / b[i]; // bảo đảm chia hết theo giả thiết đề
Delta[i] = L[i] - r[i]*L[i+1]; // 0 <= Delta < r
}
// Tính need ngược chiều
vector<long long> need(m, 0);
need[0] = 0;
for (int k = 0; k + 1 < m; ++k) {
long long Lk = L[k], rk = r[k], Dk = Delta[k];
long long M = min(Lk, (long long)cnt[k] + need[k]);
long long nxt = ceil_div_pos(M - Dk, rk);
if (nxt > L[k+1]) nxt = L[k+1];
need[k+1] = nxt;
}
auto make_pref = [&](int idx, long long K)->vector<long long> {
K = min<long long>(K, cnt[idx]);
vector<long long> pref(K + 1, 0);
long long s = 0;
for (long long i = 0; i < K; ++i) {
s += vals[idx][(int)i];
pref[(size_t)i + 1] = s;
}
return pref; // pref[t] = tổng t món đầu
};
// Base: lớp nặng nhất
vector<long long> H; // H[u] = F_{k}(L_k - u) của lớp hiện tại
{
int k = m - 1;
auto pref = make_pref(k, L[k]);
long long needk = need[k];
H.assign((size_t)needk + 1, NEG);
for (long long u = 0; u <= needk; ++u) {
long long cap = L[k] - u;
if (cap < 0) { H[(size_t)u] = NEG; continue; }
long long take = min<long long>(cnt[k], cap);
H[(size_t)u] = pref[(size_t)take];
}
}
// Xuống các lớp nhẹ hơn
for (int k = m - 2; k >= 0; --k) {
long long Lk = L[k], rk = r[k], Dk = Delta[k];
long long needk = need[k], needNext = need[k+1];
long long Kcap = min<long long>(cnt[k], Lk);
auto pref = make_pref(k, Kcap);
vector<long long> G((size_t)needk + 1, NEG);
long long maxResidue = min<long long>(rk - 1, Kcap);
for (long long j = 0; j <= maxResidue; ++j) {
// A[q] = pref[j + q*rk], q=0..len_j-1
long long len_j = (Kcap >= j) ? ((Kcap - j) / rk + 1) : 0;
if (len_j == 0) continue;
// Duyệt từng s (base) và xử lý block u tương ứng
for (long long s = 0; s <= needNext; ++s) {
// q tối đa vì cần H[s+q] tồn tại
long long xmax_s = min<long long>(len_j - 1, needNext - s);
if (xmax_s < 0) continue;
// prefix max PM[x] = max_{q<=x} ( pref[j + q*r] + H[s+q] )
vector<long long> PM((size_t)xmax_s + 1);
PM[0] = pref[(size_t)j] + H[(size_t)(s + 0)];
for (long long x = 1; x <= xmax_s; ++x) {
long long cand = pref[(size_t)(j + x*rk)] + H[(size_t)(s + x)];
PM[(size_t)x] = max(PM[(size_t)(x - 1)], cand);
}
// Block u mà base(u)=s:
long long left_u, right_u;
if (s == 0) {
right_u = Dk - j;
left_u = 0;
} else {
left_u = rk*(s - 1) - (j - Dk) + 1;
right_u = rk*s - (j - Dk);
}
if (right_u < 0 || left_u > needk) continue;
left_u = max<long long>(left_u, 0);
right_u = min<long long>(right_u, needk);
for (long long u = left_u; u <= right_u; ++u) {
long long limitA = min<long long>(Kcap, Lk - u);
if (limitA < j) continue;
long long Xu = (limitA - j) / rk; // q upper bởi sức chứa
long long xu = min<long long>(Xu, xmax_s);
G[(size_t)u] = max(G[(size_t)u], PM[(size_t)xu]);
}
}
}
H.swap(G);
}
cout << H[0] << '\n';
return 0;
}