// Temp.cpp : このファイルには 'main' 関数が含まれています。プログラム実行の開始と終了がそこで行われます。
//
// AtCoder.cpp : このファイルには 'main' 関数が含まれています。プログラム実行の開始と終了がそこで行われます。
//


#define _USE_MATH_DEFINES
#include<math.h>

//#include<cmath>

#include<deque>
#include<queue>
#include<vector>
#include<algorithm>
#include<iostream>
#include<set>
#include<cmath>
#include<tuple>
#include<string>
#include<chrono>
#include<functional>
#include<iterator>
#include<random>
#include<unordered_set>
#include<array>
#include<map>
#include<iomanip>
#include<assert.h>
#include<bitset>
#include<stack>
#include<memory>



//#include "Ants.h"
using namespace std;
typedef long long ll;
#define rad_to_deg(rad) (((rad)/2/M_PI)*360)
#define EPS (1e-7)
#define INF (1e9)
#define PI (acos(-1))
#define rep(i,n) for(int i=0;i<n;i++)
#define show(s) cout<<s<<endl
#define chmin(x,y) x=min(x,y)
#define chmax(x,y) x=max(x,y)

typedef pair < ll, ll> P;
/****Union-Ford-Tree***/
int par[int(0x7ffffff)];//Parent
int Rank[int(0x7ffffff)];//Deep_of_the_Tree

//int a[100] = { 4,2,3,1,5 }, m[100] ={3,2,2};
//int n = 0;
//const ll mod = 1000000007;


//n要素で初期化
void init(int n) {
	for (int i = 0; i < n; i++) {
		par[i] = i;
		Rank[i] = 0;
	}
}
//木の根探し
int find(int x) {
	if (par[x] == x) {
		return x;
	}
	else {
		return par[x] = find(par[x]);
	}
}
//xとyの属する集合を併合
void unite(int x, int y) {
	x = find(x);
	y = find(y);
	if (x == y)return;

	if (Rank[x] < Rank[y]) {
		par[x] = y;
	}
	else {
		par[y] = x;
		if (Rank[x] == Rank[y])Rank[x]++;
	}
}

bool same(int x, int y) {
	return find(x) == find(y);
}

/************************************************************************************
//*無向グラフのBELL_MAN_FORD;*//*
struct edge { int from, to, cost; };
edge es[10000];
int d[10000];
void shortest_path(int s) {
	for (int i = 0; i < V; i++)d[i] = INF;
	d[s] = 0;
	while (true) {
		bool update = false;
		for (int i = 0; i < E*2; i++) {
			edge e = es[i];
			if (d[e.from] != INF && d[e.to] > d[e.from] + e.cost) {
				d[e.to] = d[e.from] + e.cost;
				update = true;
			}
		}
		if (!update)break;
	}

}
int main() {
	cin >> V >> E;
	for (int i = 0; i < E*2; i = i+2) {
		edge e;
		cin >> e.from >> e.to >> e.cost;
		es[i] = e;
		swap(e.to, e.from);
		es[i + 1] = e;

	}
	shortest_path(0);
	cout << d[6] << endl;
}
*////////////////////////////////////////*
ll gcd(ll a, ll b) {//最大公約数を求める
	if (b == 0) return a;
	return gcd(b, a % b);
}
/*****************************************************************************/
//int dx[4] = { 1,0,-1,0 }, dy[4] = { 0,1,0,-1};
//vector<int> G[10000];


//struct edge { int to, cost; };
//vector<edge>G[100000];


/**************/


/****
//priority_queue<int> //大きいものから順番に出てくる
//priority_queue<int,vector<int>,greater<int>>//小さいものから出てくる（順位キュー）
.top();
.pop();
.empty();
**/
//void solve();
//int V, E;
//void shortest_path(int);
/*
typedef long double ld;
typedef unsigned long long ull;
const int MX = 3007;

const int  MAX_N = 1 << 17;

//セグメント木
int Q, dat[2 * MAX_N - 1];
//要素数で初期化
void rmq_init(int n_) {
	Q = 1;
	while (Q < n_)Q *= 2;
	for (int i = 0; i < 2 * Q - 1; i++)dat[i] = INF;

}
//k番目をaに変更
void update(int k, int a) {
	k += Q - 1;
	dat[k] = a;
	while (k > 0) {
		k = (k - 1) / 2;
		dat[k] = min(dat[k * 2 + 1], dat[k * 2 + 2]);
	}
}
//最小値探し(a,b,0,0,n)
int query(int a, int b, int k = 0, int l = 0, int r = Q) {

	if (r <= a || b <= l)return INF;

	if (a <= l && r <= b)return dat[k];
	else {

		int vl = query(a, b, k * 2 + 1, l, (l + r) / 2);
		int vr = query(a, b, k * 2 + 2, (l + r) / 2, r);
		return min(vl, vr);
	}
}


ll mod_pow(ll x, ll n, ll mod) {
	if (n == 0)return 1;
	ll res = mod_pow(x * x % mod, n / 2, mod);
	if (n & 1)res = res * x % mod;
	//cout << res << endl;
	return res;
}

ll power(ll x, ll purpose, ll mod = ll(1e18), ll n = 63) {
	ll res = 1;
	while (n > 0 && res < purpose) {
		//cout << res << endl;
		if (n & 1)res = res * x % mod;
		x = x * x % mod;
		n >>= 1;
	}
	return res;
}

//vector<P> que;


typedef vector<ll> vec;
typedef vector<vec> mat;
const int M = 4;

//A*B　Matrix
mat mul(mat& A, mat& B) {
	mat C(A.size(), vec(B[0].size()));
	for (int i = 0; i < A.size(); i++)for (int k = 0; k < B.size(); k++)
		for (int j = 0; j < B[0].size(); j++)
			C[i][j] = (C[i][j] + A[i][k] * B[k][j]) % M;

	return C;
}

//A^n　行列専用
mat pow(mat A, ll n) {
	mat B(A.size(), vec(A.size()));
	for (int i = 0; i < A.size(); i++)B[i][i] = 1;
	while (n > 0) {
		if (n & 1)B = mul(B, A);
		A = mul(A, A);
		n >>= 1;
	}
	return B;
}
int IsPrime(ll num)
{
	if (num < 2) return 1;
	else if (num == 2) return 2;
	else if (num % 2 == 0) return 2; // 偶数はあらかじめ除く

	double sqrtNum = sqrt(num);
	for (ll i = 3; i <= sqrtNum; i += 2)
	{
		if (num % i == 0)
		{
			// 素数ではない
			return i;
		}
	}

	// 素数である
	return 0;
}*/
/*
vector<ll> divisor(ll n) {
	vector<ll> res;
	for (ll i = 1; i * i <= n; i++) {
		if (n % i == 0) {

			res.push_back(i);
			//if (i != n / i)res.push_back(n / i);

		}
	}


	return res;
}*/
/*
map<ll, ll> prime_factor(ll n) {
	map<ll, ll>res;
	for (ll i = 2; i * i <= n; i++) {
		while (n % i == 0) {
			++res[i];
			n /= i;
		}
	}
	if (n != 1)res[n] = 1;
	return res;
}*/
/***************///オイラーの小定理による組み合わせ//*********/
/*static const ll N = 1e6+5;
const ll MOD = 1e9+7;
ll fact[N], invfact[N];
ll pow(ll a, ll b, ll m)
{
	ll ans = 1;
	while (b)
	{
		if (b & 1)
			ans = (ans * a) % m;
		b /= 2;
		a = (a * a) % m;
	}
	return ans;
}

ll modinv(ll k)
{
	return pow(k, MOD - 2, MOD);
}

void precompute()
{
	fact[0] = fact[1] = 1;
	for (ll i = 2; i < N; i++)
	{
		fact[i] = fact[i - 1] * i;
		fact[i] %= MOD;
	}
	invfact[N - 1] = modinv(fact[N - 1]);
	for (ll i = N - 2; i >= 0; i--)
	{
		invfact[i] = invfact[i + 1] * (i + 1);
		invfact[i] %= MOD;
	}
}

ll nCr(ll x, ll y)
{
	if (y > x)
		return 0;

	ll num = fact[x];
	num *= invfact[y];
	num %= MOD;
	num *= invfact[x - y];
	num %= MOD;
	return num;
}
/************************組み合わせおわり*****************************/

int N, T;
struct food {
	int A, B;
};
food dish[100000];
ll dp[10000][10000];
bool comp(const food& a, const food& b) {
	return a.B < b.B;
}
bool used[1000000];
int main() {
	cin >> N >> T;
	rep(i, N) {
		int a, b;
		cin >> a >> b;
		dish[i] = { a,b };
	}
	sort(dish, dish + N, comp);
	for (int i = 1; i <= N; i++) {
		for (int j = 0; j < T; j++) {

			if (j - dish[i - 1].A >= 0) {
				if (dp[i - 1][j - dish[i - 1].A] + dish[i - 1].B > dp[i - 1][j]) {

				}
				dp[i][j] = max(dp[i - 1][j - dish[i - 1].A] + dish[i - 1].B, dp[i - 1][j]);
			}
			else {
				dp[i][j] = dp[i - 1][j];
			}

		}
	}
	ll res = 0, t = 0;
	ll spe = 0;
	for (int i = T - 1; i >= 0; --i) {
		spe = max(dp[N - 1][i], spe);
		if (dp[N][i] > res) {
			res = dp[N][i];
			t = i;
		}
	}
	for (int i = N; i >= 1; --i) {
		ll x = dp[i][t];
		if (dp[i - 1][t] != x) {
			used[i - 1] = true;
			t = t - dish[i - 1].A;
		}
	}
	int ress = 0;
	rep(i, N) {
		if (!used[i]) {
			
			ress = max(ress, dish[i].B);
		}
	}
	cout << max(dp[N][T - 1]+ress,spe+dish[N-1].B) << endl;

}

Measured memory_bytes: 238208