#include <bits/stdc++.h>
using namespace std;
using int64 = long long;
const long long NEG = (long long)-4e18;
static inline long long ceil_div_nonneg(long long x, long long r) {
if (x <= 0) return 0LL; // clamp lên 0 (không mượn thêm sức chứa ở lớp trên)
return (x + r - 1) / r; // x>0
}
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>> bucket;
bucket.reserve(N*2);
vector<pair<long long,long long>> items;
items.reserve(N);
for (int i = 0; i < N; ++i) {
long long v, w;
cin >> v >> w;
bucket[w].push_back(v);
}
// Lấy danh sách weight phân biệt và sort tăng
vector<long long> b;
b.reserve(bucket.size());
for (auto &kv : bucket) {
b.push_back(kv.first);
}
sort(b.begin(), b.end()); // b[0] nhỏ nhất
int m = (int)b.size();
vector<vector<long long>> vals(m);
vector<int> cnt(m);
for (int i = 0; i < m; ++i) {
auto &vec = bucket[b[i]];
sort(vec.begin(), vec.end(), greater<long long>());
vals[i] = move(vec);
cnt[i] = (int)vals[i].size();
}
// Tính L_k = floor(W / b_k), r_k = b_{k+1}/b_k, Delta_k = L_k - r_k * L_{k+1}
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) {
// Với đề bài, b[i+1] % b[i] == 0 luôn đúng (do dãy chia hết)
r[i] = b[i+1] / b[i];
Delta[i] = L[i] - r[i]*L[i+1];
// assert(0 <= Delta[i] && Delta[i] < r[i]);
}
// Tính "need" ngược chiều: need[k] = cần F_k(L_k - t) với t ∈ [0..need[k]]
// Ban đầu chỉ cần F_0(L_0) -> need[0] = 0
vector<long long> need(m, 0);
need[0] = 0;
for (int k = 0; k+1 < m; ++k) {
long long Lk = L[k];
long long rk = r[k];
long long Dk = Delta[k];
long long needk = need[k];
long long Ck = cnt[k];
// M = max_{t∈[0..needk]} ( t + min(Ck, Lk - t) ) = min(Lk, needk + Ck)
long long M = min(Lk, needk + Ck);
long long x = M - Dk;
long long nextNeed = (x <= 0) ? 0 : ( (x + rk - 1) / rk );
if (nextNeed > L[k+1]) nextNeed = L[k+1];
need[k+1] = nextNeed;
}
// Hàm xây prefix sum top-K của 1 nhóm (chỉ cần đến min(cnt, Klimit))
auto build_prefix = [&](int idx, long long Klimit)->vector<long long> {
long long K = min<long long>(cnt[idx], Klimit);
vector<long long> pref;
pref.reserve((size_t)K + 1);
pref.push_back(0);
long long acc = 0;
for (long long i = 0; i < K; ++i) {
acc += vals[idx][(int)i];
pref.push_back(acc);
}
return pref; // pref[t] = tổng t món tốt nhất
};
// Base: lớp nặng nhất m-1
vector<long long> h; // h[u] = F_{k}(L_k - u) tại lớp hiện tại (khởi đầu: k = m-1)
{
int k = m-1;
long long needk = need[k];
vector<long long> pref_last = build_prefix(k, L[k]); // pick ≤ min(cnt, L[k])
h.assign((size_t)needk + 1, NEG);
for (long long u = 0; u <= needk; ++u) {
long long cap = L[k] - u; // còn lại bao nhiêu slot của b_k
if (cap < 0) { h[(size_t)u] = NEG; continue; }
long long take = min<long long>(cnt[k], cap);
h[(size_t)u] = pref_last[(size_t)take];
}
}
// Chuyển từ lớp k+1 -> k, lần lượt k = m-2..0
for (int k = m-2; k >= 0; --k) {
long long Lk = L[k];
long long rk = r[k];
long long Dk = Delta[k];
long long needk = need[k];
long long needNext = need[k+1];
// prefix của nhóm k (chỉ cần đến K = min(cnt, Lk))
long long Kcap = min<long long>(cnt[k], Lk);
vector<long long> pref_k = build_prefix(k, Kcap);
// Ghi chú: với j > Kcap thì không có a ≡ j (mod rk) khả dụng.
long long maxResidue = min<long long>(rk-1, Kcap);
// Kết quả g[t] = F_k(Lk - t), t=0..needk
vector<long long> g((size_t)needk + 1, NEG);
// Với mỗi residue j, ta duyệt tất cả s = ceil((t + j - Dk)/rk) theo block t
for (long long j = 0; j <= maxResidue; ++j) {
// A_j[x] = pref_k[j + x*rk], x = 0..len_j-1
long long len_j = (Kcap >= j) ? ((Kcap - j) / rk + 1) : 0;
if (len_j == 0) continue;
// Tiền xử lý theo từng s: PM_s[x] = max_{q<=x} ( A_j[q] + h[s+q] )
// Nhưng chỉ cần đến x ≤ min(len_j-1, needNext - s)
// Để tiết kiệm bộ nhớ, làm s lần lượt, build 1 mảng PM rồi đổ vào các t trong block của (j,s)
for (long long s = 0; s <= needNext; ++s) {
long long xmax_s = min<long long>(len_j - 1, needNext - s);
if (xmax_s < 0) continue;
vector<long long> PM;
PM.resize((size_t)xmax_s + 1);
// PM[0]
PM[0] = pref_k[(size_t)(j)] + h[(size_t)(s + 0)];
for (long long x = 1; x <= xmax_s; ++x) {
long long val = pref_k[(size_t)(j + x * rk)] + h[(size_t)(s + x)];
PM[(size_t)x] = max(PM[(size_t)(x - 1)], val);
}
// t thuộc block: base_j(t) = s <=> rk*s - (j - Dk) ≤ t ≤ rk*s + rk - 1 - (j - Dk)
long long left_t = rk * s - (j - Dk);
long long right_t = rk * s + rk - 1 - (j - Dk);
if (right_t < 0 || left_t > needk) continue;
left_t = max<long long>(left_t, 0);
right_t = min<long long>(right_t, needk);
for (long long t = left_t; t <= right_t; ++t) {
// Giới hạn a ≤ min(Kcap, Lk - t) và a ≡ j (mod rk)
long long limitA = min<long long>(Kcap, Lk - t);
if (limitA < j) continue;
long long Xt = (limitA - j) / rk;
long long xu = min<long long>(Xt, xmax_s);
// PM[xu] là max của pref[j + q*r] + h[s+q] với q ≤ xu
g[(size_t)t] = max(g[(size_t)t], PM[(size_t)xu]);
}
}
}
h.swap(g); // h trở thành F_k(Lk - t) cho t ∈ [0..needk]
}
cout << h[0] << '\n';
return 0;
}