//#pragma GCC optimize("O3")
//#pragma GCC target("avx")

#include <algorithm>
#include <cmath>
#include <functional>
#include <iomanip>
#include <iostream>
#include <iterator>
#include <list>
#include <map>
#include <numeric>
#include <queue>
#include <random>
#include <set>
#include <sstream>
#include <string>
#include <type_traits>
#include <unordered_map>
#include <unordered_set>
#include <utility>
#include <vector>

using namespace std;

// Types
using ll = long long int;
using str = string;
template <class T> using u_set = unordered_set<T>;
template <class K, class V> using u_map = unordered_map<K, V>;
template <class V> using graph = u_map<V, u_set<V>>;
template <class F,
          typename enable_if<is_function<F>::value, nullptr_t>::type = nullptr>
using fun = function<F>;

namespace io {
// Input
using IS = istream;
IS &in(IS &);
IS &in(IS &i) { return i; }
template <class T, class... Ts> IS &in(IS &i, T &a, Ts &... as) {
  return in(i >> a, as...);
}
template <class V> IS &operator>>(IS &i, vector<V> &a) {
  for_each(a.begin(), a.end(), [&i](auto &e) { i >> e; });
  return i;
}
template <class F, class S> IS &operator>>(IS &i, pair<F, S> &p) {
  return in(i, p.first, p.second);
}
template <class... Ts, size_t... I>
IS &tuple_in(IS &i, tuple<Ts...> &t, index_sequence<I...>) {
  return in(i, get<I>(t)...);
}
template <class... Ts> IS &operator>>(IS &i, tuple<Ts...> &t) {
  return tuple_in(i, t, make_index_sequence<sizeof...(Ts)>());
}
// Output
using OS = ostream;
OS &out(OS &o) { return o << endl; }
template <class T> OS &out(OS &o, T const &a) { return o << a << endl; }
template <class T, class... Ts> OS &out(OS &o, T const &a, Ts const &... as) {
  return out(o << a << " ", as...);
}
template <class F, class S> OS &operator<<(OS &o, pair<F, S> const &p) {
  return o << p.first << ":" << p.second;
}
template <class... Ts> OS &args_out(OS &o, Ts const &... ts);
OS &args_out(OS &o) { return o; }
template <class T> OS &args_out(OS &o, T const &t) { return o << t; }
template <class T0, class T1, class... Ts>
OS &args_out(OS &o, T0 const &t0, T1 const &t1, Ts const &... ts) {
  return args_out(o << t0 << ",", t1, ts...);
}
template <class... Ts, size_t... I>
OS &tuple_out(OS &o, tuple<Ts...> const &t, index_sequence<I...>) {
  return args_out(o, get<I>(t)...);
}
template <class... Ts> OS &operator<<(OS &o, tuple<Ts...> const &t) {
  return tuple_out(o << "(", t, make_index_sequence<sizeof...(Ts)>()) << ")";
}
template <
    class C,
    class T = typename iterator_traits<typename C::iterator>::value_type,
    typename enable_if<!is_same<C, str>::value, nullptr_t>::type = nullptr>
OS &operator<<(OS &o, C const &a) {
  return a.empty() ? (o << "[]") : ([&o, &a]() -> OS & {
    o << "[" << *a.begin();
    for_each(next(a.begin()), a.end(), [&o](auto const &e) { o << "," << e; });
    return o << "]";
  }());
}
} // namespace io
auto input = [](auto &... a) { io::in(cin, a...); };
auto print = [](auto const &... a) { io::out(cout, a...); };
#ifdef JUMPAKU_DEBUG
auto dump = [](auto const &... a) { io::out(cerr, a...); };
#else
auto dump = [](auto const &...) {};
#endif

// Hash
namespace hashcode {
template <class... Ts> size_t hash_args(size_t h, Ts const &... ts);
size_t hash_args(size_t h) { return h; }
template <class T, class... Ts>
size_t hash_args(size_t h, T const &t, Ts const &... ts) {
  return hash_args(((h << 10) - h + hash<T>{}(t)), ts...);
}
template <class... Ts, size_t... I>
size_t hash_tuple(tuple<Ts...> const &t, index_sequence<I...>) {
  return hash_args(17, get<I>(t)...);
}
} // namespace hashcode
namespace std {
template <class... Ts> struct hash<tuple<Ts...>> {
  size_t operator()(tuple<Ts...> const &t) const {
    return hashcode::hash_tuple(t, index_sequence_for<Ts...>());
  }
};
} // namespace std

// Range
vector<ll> range(ll const &begin, ll const &end) {
  vector<ll> ret(max(0LL, end - begin));
  iota(ret.begin(), ret.end(), begin);
  return ret;
}
vector<ll> range(ll const &end) { return range(0, end); }
template <class T = ll> vector<T> vec(size_t n, T &&init = T()) {
  return vector<T>(n, init);
}

ll bisect(ll ng, ll ok, fun<bool(ll)> const &is_ok) {
  while (ok - ng > 1) {
    dump(ng, ok);
    ll m = ng + (ok - ng) / 2;
    (is_ok(m) ? ok : ng) = m;
  }
  return ok;
}

ll gcd(ll p, ll q) { return (q == 0) ? p : gcd(q, p % q); }
ll lcm(ll p, ll q) { return p / gcd(q, p) * q; }

// MOD
ll pow(ll a, ll n, ll m) {
  if (n == 0)
    return 1;
  if (n & 1)
    return ((a % m) * pow(a, n - 1, m)) % m;
  auto b = pow(a, n / 2, m);
  return (b * b) % m;
}
ll inv(ll a, ll p) { return pow(a, p - 2, p); }

template <ll N, ll M> struct Factrial {
  Factrial() {
    fact[0] = 1;
    for (int i = 1; i <= N; ++i)
      fact[i] = (fact[i - 1] * i) % M;
    finv[N] = inv(fact[N], M);
    for (int i = N; i > 0; --i)
      finv[i - 1] = (finv[i] * i) % M;
  }
  array<ll, N + 1> fact;
  array<ll, N + 1> finv;
  ll comb(ll n, ll m) const {
    if (m < 0 || n < m)
      return 0LL;
    return (fact[n] * ((finv[m] * finv[n - m]) % M)) % M;
  }
};

constexpr ll MOD = 1e9 + 7;

template <class V = ll, class W = ll> using w_graph = u_map<V, u_map<V, W>>;

#include <memory>

class my_map {
  using K = tuple<ll, ll>;
  using V = ll;
  size_t bucket_size;
  using Hasher = hash<K>;
  Hasher const hasher = Hasher{};
  // using Equal = hash<K>;
  size_t hash_fun(K const &key) const { return hasher(key); }
  size_t index(size_t const &hash) const { return hash % bucket_size; }
  map<K, V> *buckets;

public:
  my_map(size_t bucket_size)
      : bucket_size(bucket_size), buckets(new map<K, V>[(bucket_size)]) {}
  V &operator[](K const &key) {
    auto idx = index(hash_fun(key));
    // if (buckets[idx] == nullptr)
    // buckets[idx] = make_unique<map<K, V>>();
    return (buckets[idx])[key];
  }
  V const &at(K const &key) const {
    auto idx = index(hash_fun(key));
    return buckets[idx].at(key);
  }
  bool contains(K const &key) const {
    auto idx = index(hash_fun(key));
    return // buckets[idx] != nullptr &&
        buckets[idx].find(key) != buckets[idx].end();
  }
};

using Key = tuple<ll, ll>;
ll f(Key const &key, vector<tuple<ll, ll>> const &AB, u_map<Key, ll> &dp) {
  ll t = get<0>(key);
  ll i = get<1>(key);
  if (t <= 0)
    return 0;
  auto Ai = get<0>(AB[i]);
  auto Bi = get<1>(AB[i]);
  if (i == 0)
    return Bi;
  // if (dp.contains(key))
  if (dp.find(key) != dp.end())
    return dp[key];
  return dp[key] =
             max(f(Key{t - Ai, i - 1}, AB, dp) + Bi, f(Key{t, i - 1}, AB, dp));
}
int main() {
  ll N, T;
  input(N, T);
  auto AB = vec<tuple<ll, ll>>(N);
  input(AB);
  sort(AB.begin(), AB.end(),
       [](auto const &x0, auto const &x1) { return get<0>(x0) > get<0>(x1); });
  // dump(N, T, AB);
  auto dp = u_map<Key, ll>{}; //
  dp.reserve(1e7);
  print(f(Key{T, N - 1}, AB, dp));
}
/*
int main() {
  ll N, M, T;
  input(N, M, T);
  auto A = vec(N);
  input(A);
  auto g = graph<ll>{};
  using E = tuple<ll, ll>;
  auto edges = u_map<E, ll>{};
  for (auto _ : range(M)) {
    ll a, b, c;
    input(a, b, c);
    g[a].insert(b);
    edges[E{a, b}] = c;
  }
  dump(N, M, T, A, g);
  ll start = 1;
  constexpr ll INF = 1e18;
  auto d = u_map<ll, ll>{};
  auto q = multimap<ll, ll>{};
  for (auto &&i : range(1, N + 1))
    q.insert(make_pair(d[i] = ((i == start) ? 0 : INF), i));
  while (!q.empty()) {
    auto itr = q.begin();
    auto qu = itr->first;
    auto u = itr->second;
    q.erase(itr);
    for (auto const &v : g[u]) {
      auto alt = d[u] + edges[E{u, v}];
      dump(u, v, d[v], alt);
      if (d[v] > alt) {
        d[v] = alt;
        q.insert(make_pair(alt, v));
      }
    }
  }
  dump(d);
  auto ans = 1e18;
  for (auto &&v : range(1, N + 1)) {
  }
}
*/

Measured memory_bytes: 487680