// SNIPPET bitset
const BITSET_TRUE: &'static bool = &true;
const BITSET_FALSE: &'static bool = &false;
#[derive(Clone, Debug)]
pub struct BitSet {
    buf: Vec<u64>,
    size: usize,
}
impl BitSet {
    pub fn new(size: usize) -> BitSet {
        BitSet {
            buf: vec![0; (size + 63) / 64],
            size: size,
        }
    }
    pub fn get(&mut self, i: usize) -> Option<bool> {
        if i <= self.size {
            Some(self.buf[i >> 6] & 1 << (i & 63) != 0)
        } else {
            None
        }
    }
    pub fn get_mut(&mut self, i: usize) -> Option<BitSetRef> {
        self.get(i).map(move |b| BitSetRef {
            bitset: self,
            value: b,
            index: i
        })
    }
    pub fn at(&mut self, i: usize) -> BitSetRef {
        self.get_mut(i).unwrap()
    }
    pub fn count_ones(&self) -> u32 {
        self.buf.iter().map(|x| x.count_ones()).sum()
    }
    fn chomp(&mut self) {
        let r = self.size & 63;
        if r != 0 {
            if let Some(x) = self.buf.last_mut() {
                let d = 64 - r;
                *x = (*x << d) >> d;
            }
        }
    }
}
impl std::ops::Index<usize> for BitSet {
    type Output = bool;
    fn index(&self, index: usize) -> &bool {
        [BITSET_FALSE, BITSET_TRUE][(self.buf[index >> 6] >> (index & 63)) as usize & 1]
    }
}
pub struct BitSetRef<'a> {
    bitset: &'a mut BitSet,
    value: bool,
    index: usize
}
impl<'a> std::ops::Deref for BitSetRef<'a> {
    type Target = bool;
    fn deref(&self) -> &bool {
        &self.value
    }
}
impl<'a> std::ops::DerefMut for BitSetRef<'a> {
    fn deref_mut(&mut self) -> &mut bool {
        &mut self.value
    }
}
impl<'a> Drop for BitSetRef<'a> {
    fn drop(&mut self) {
        if self.value {
            self.bitset.buf[self.index >> 6] |= 1 << (self.index & 63);
        } else {
            self.bitset.buf[self.index >> 6] &= !(1 << (self.index & 63));
        }
    }
}
impl std::ops::ShlAssign<usize> for BitSet {
    fn shl_assign(&mut self, x: usize) {
        let q = x >> 6;
        let r = x & 63;
        if q >= self.buf.len() {
            for x in &mut self.buf {
                *x = 0;
            }
            return;
        }
        if r == 0 {
            for i in (q..self.buf.len()).rev() {
                self.buf[i] = self.buf[i - q];
            }
        } else {
            for i in (q + 1..self.buf.len()).rev() {
                self.buf[i] = (self.buf[i - q] << r) | (self.buf[i - q - 1] >> (64 - r));
            }
            self.buf[q] = self.buf[0] << r;
        }
        for x in &mut self.buf[..q] {
            *x = 0;
        }
        self.chomp();
    }
}
impl std::ops::Shl<usize> for BitSet {
    type Output = Self;
    fn shl(mut self, x: usize) -> Self {
        self <<= x;
        self
    }
}
impl std::ops::ShrAssign<usize> for BitSet {
    fn shr_assign(&mut self, x: usize) {
        let q = x >> 6;
        let r = x & 63;
        if q >= self.buf.len() {
            for x in &mut self.buf {
                *x = 0;
            }
            return;
        }
        if r == 0 {
            for i in 0..self.buf.len() - q {
                self.buf[i] = self.buf[i + q];
            }
        } else {
            for i in 0..self.buf.len() - q - 1 {
                self.buf[i] = (self.buf[i + q] >> r) | (self.buf[i + q + 1] << (64 - r));
            }
            let len = self.buf.len();
            self.buf[len - q - 1] = self.buf[len - 1] >> r;
        }
        let len = self.buf.len();
        for x in &mut self.buf[len - q..] {
            *x = 0;
        }
    }
}
impl std::ops::Shr<usize> for BitSet {
    type Output = Self;
    fn shr(mut self, x: usize) -> Self {
        self >>= x;
        self
    }
}
impl<'a> std::ops::BitAndAssign<&'a BitSet> for BitSet {
    fn bitand_assign(&mut self, rhs: &'a Self) {
        for (a, b) in self.buf.iter_mut().zip(rhs.buf.iter()) {
            *a &= *b;
        }
    }
}
impl<'a> std::ops::BitAnd<&'a BitSet> for BitSet {
    type Output = Self;
    fn bitand(mut self, rhs: &'a Self) -> Self {
        self &= rhs;
        self
    }
}
impl<'a> std::ops::BitOrAssign<&'a BitSet> for BitSet {
    fn bitor_assign(&mut self, rhs: &'a Self) {
        for (a, b) in self.buf.iter_mut().zip(rhs.buf.iter()) {
            *a |= *b;
        }
        self.chomp();
    }
}
impl<'a> std::ops::BitOr<&'a BitSet> for BitSet {
    type Output = Self;
    fn bitor(mut self, rhs: &'a Self) -> Self {
        self |= rhs;
        self
    }
}
impl<'a> std::ops::BitXorAssign<&'a BitSet> for BitSet {
    fn bitxor_assign(&mut self, rhs: &'a Self) {
        for (a, b) in self.buf.iter_mut().zip(rhs.buf.iter()) {
            *a ^= *b;
        }
        self.chomp();
    }
}
impl<'a> std::ops::BitXor<&'a BitSet> for BitSet {
    type Output = Self;
    fn bitxor(mut self, rhs: &'a Self) -> Self {
        self ^= rhs;
        self
    }
}
// SNIPPET read
pub trait Readable {
    type Output;
    fn words_count() -> usize;
    fn read_words(words: &[&str]) -> Result<Self::Output, String>;
}
#[macro_export]
macro_rules! readable {
    ( $t:ty, $words_count:expr, |$words:ident| $read_words:expr ) => {
        impl Readable for $t {
            type Output = $t;
            fn words_count() -> usize { $words_count }
            fn read_words($words: &[&str]) -> Result<$t, String> {
                Ok($read_words)
            }
        }
    };
}
readable!((), 1, |_ss| ());
readable!(String, 1, |ss| ss[0].to_string());
impl Readable for char {
    type Output = char;
    fn words_count() -> usize { 1 }
    fn read_words(words: &[&str]) -> Result<char, String> {
        let chars: Vec<char> = words[0].chars().collect();
        if chars.len() == 1 {
            Ok(chars[0])
        } else {
            Err(format!("cannot parse `{}` as a char", words[0]))
        }
    }
}
pub struct Chars();
impl Readable for Chars {
    type Output = Vec<char>;
    fn words_count() -> usize { 1 }
    fn read_words(words: &[&str]) -> Result<Vec<char>, String> {
        Ok(words[0].chars().collect())
    }
}
macro_rules! impl_readable_for_ints {
    ( $( $t:ty )* ) => { $(
        impl Readable for $t {
            type Output = Self;
            fn words_count() -> usize { 1 }
            fn read_words(words: &[&str]) -> Result<$t, String> {
                use std::str::FromStr;
                <$t>::from_str(words[0]).map_err(|_| {
                    format!("cannot parse `{}` as {}", words[0], stringify!($t))
                })
            }
        }
    )* };
}
impl_readable_for_ints!(i8 u8 i16 u16 i32 u32 i64 u64 isize usize f32 f64);
macro_rules! define_one_origin_int_types {
    ( $new_t:ident $int_t:ty ) => {
        #[allow(non_camel_case_types)]
        pub struct $new_t;
        impl Readable for $new_t {
            type Output = $int_t;
            fn words_count() -> usize { 1 }
            fn read_words(words: &[&str]) -> Result<Self::Output, String> {
                <$int_t>::read_words(words).map(|n| n-1)
            }
        }
    };
    ( $new_t:ident $int_t:ty; $( $inner_new_t:ident $inner_int_t:ty );* ) => {
        define_one_origin_int_types!($new_t $int_t);
        define_one_origin_int_types!($($inner_new_t $inner_int_t);*);
    };
}
define_one_origin_int_types!(u8_ u8; u16_ u16; u32_ u32; u64_ u64; usize_ usize);
macro_rules! impl_readable_for_tuples {
    ( $t:ident $var:ident ) => ();
    ( $t:ident $var:ident; $( $inner_t:ident $inner_var:ident );* ) => {
        impl_readable_for_tuples!($($inner_t $inner_var);*);
        impl<$t: Readable, $($inner_t: Readable),*> Readable
            for ($t, $($inner_t),*)
        {
            type Output = ( <$t>::Output, $(<$inner_t>::Output),* );
            fn words_count() -> usize {
                let mut n = <$t>::words_count();
                $(
                    n += <$inner_t>::words_count();
                )*
                n
            }
            #[allow(unused_assignments)]
            fn read_words(words: &[&str]) ->
                Result<Self::Output, String>
            {
                let mut start = 0;
                let $var = <$t>::read_words(
                    &words[start .. start+<$t>::words_count()]
                )?;
                start += <$t>::words_count();
                $(
                    let $inner_var =
                        <$inner_t>::read_words(
                            &words[start .. start+<$inner_t>::words_count()]
                        )?;
                    start += <$inner_t>::words_count();
                )*
                Ok(( $var, $($inner_var),* ))
            }
        }
    };
}
impl_readable_for_tuples!(T8 x8; T7 x7; T6 x6; T5 x5; T4 x4; T3 x3; T2 x2; T1 x1);
pub trait ReadableFromLine {
    type Output;
    fn read_line(line: &str) -> Result<Self::Output, String>;
}
fn split_into_words(line: &str) -> Vec<&str> {
    #[allow(deprecated)]
    line.trim_right_matches('\n').split_whitespace().collect()
}
impl<T: Readable> ReadableFromLine for T {
    type Output = T::Output;
    fn read_line(line: &str) -> Result<T::Output, String> {
        let words = split_into_words(line);
        if words.len() != T::words_count() {
            return Err(format!("line `{}` has {} words, expected {}",
                               line, words.len(), T::words_count()));
        }
        T::read_words(&words)
    }
}
macro_rules! impl_readable_from_line_for_tuples_with_from_iterator {
    ( $u:ident : $( + $bound:path )* => $seq_in:ty, $seq_out:ty; $t:ident $var:ident ) => {
        impl<$u: Readable> ReadableFromLine for $seq_in
        where
            <$u as Readable>::Output: Sized $(+ $bound)*
        {
            type Output = $seq_out;
            fn read_line(line: &str) -> Result<$seq_out, String> {
                let n = $u::words_count();
                let words = split_into_words(line);
                if words.len() % n != 0 {
                    return Err(format!("line `{}` has {} words, expected multiple of {}",
                                       line, words.len(), n));
                }
                let mut result = Vec::new();
                for chunk in words.chunks(n) {
                    match $u::read_words(chunk) {
                        Ok(v) => result.push(v),
                        Err(msg) => {
                            let flagment_msg = if n == 1 {
                                format!("word {}", result.len())
                            } else {
                                let l = result.len();
                                format!("words {}-{}", n*l + 1, (n+1) * l)
                            };
                            return Err(format!(
                                "{} of line `{}`: {}", flagment_msg, line, msg
                            ));
                        }
                    }
                }
                Ok(result.into_iter().collect())
            }
        }
        impl<T: Readable, $u: Readable> ReadableFromLine for (T, $seq_in)
        where
            <$u as Readable>::Output: Sized $(+ $bound)*
        {
            type Output = (T::Output, $seq_out);
            fn read_line(line: &str) -> Result<Self::Output, String> {
                let n = T::words_count();
                #[allow(deprecated)]
                let trimmed = line.trim_right_matches('\n');
                let words_and_rest: Vec<&str> = trimmed.splitn(n + 1, ' ').collect();
                if words_and_rest.len() < n {
                    return Err(format!("line `{}` has {} words, expected at least {}",
                                       line, words_and_rest.len(), n));
                }
                let words = &words_and_rest[..n];
                let empty_str = "";
                let rest = words_and_rest.get(n).unwrap_or(&empty_str);
                Ok((T::read_words(words)?, <$seq_in>::read_line(rest)?))
            }
        }
    };
    ( $u:ident : $( + $bound:path )* => $seq_in:ty, $seq_out:ty;
      $t:ident $var:ident, $( $inner_t:ident $inner_var:ident ),+ ) => {
        impl_readable_from_line_for_tuples_with_from_iterator!(
            $u: $(+ $bound)* => $seq_in, $seq_out; $($inner_t $inner_var),+
        );
        impl<$t: Readable, $($inner_t: Readable),+ , $u: Readable> ReadableFromLine
            for ($t, $($inner_t),+ , $seq_in)
        where
            <$u as Readable>::Output: Sized $(+ $bound)*
        {
            type Output = ($t::Output, $($inner_t::Output),+ , $seq_out);
            fn read_line(line: &str) -> Result<Self::Output, String> {
                let mut n = $t::words_count();
                $(
                    n += $inner_t::words_count();
                )+
                #[allow(deprecated)]
                let trimmed = line.trim_right_matches('\n');
                let words_and_rest: Vec<&str> = trimmed.splitn(n + 1, ' ')
                    .collect();
                if words_and_rest.len() < n {
                    return Err(
                        format!("line `{}` has {} words, expected at least {}",
                                line, words_and_rest.len(), n)
                    );
                }
                let words = &words_and_rest[..n];
                let empty_str = "";
                let rest = words_and_rest.get(n).unwrap_or(&empty_str);
                let ($var, $($inner_var),*) =
                    <($t, $($inner_t),+)>::read_words(words)?;
                Ok(($var, $($inner_var),* , <$seq_in>::read_line(rest)?))
            }
        }
    };
}
#[macro_export]
macro_rules! readable_collection {
    ($u:ident => $collection_in:ty, $collection_out:ty) => {
        impl_readable_from_line_for_tuples_with_from_iterator!(
            $u: => $collection_in, $collection_out;
            T8 x8, T7 x7, T6 x6, T5 x5, T4 x4, T3 x3, T2 x2, T1 x1
        );
    };
    ($u:ident : $( $bound:path ),* => $collection_in:ty, $collection_out:ty) => {
        impl_readable_from_line_for_tuples_with_from_iterator!(
            $u: $(+ $bound)* => $collection_in, $collection_out;
            T8 x8, T7 x7, T6 x6, T5 x5, T4 x4, T3 x3, T2 x2, T1 x1
        );
    }
}
readable_collection!(U => Vec<U>, Vec<U::Output>);
readable_collection!(
    U => std::collections::VecDeque<U>, std::collections::VecDeque<U::Output>
);
readable_collection!(
    U: Eq, std::hash::Hash => std::collections::HashSet<U>, std::collections::HashSet<U::Output>
);
readable_collection!(
    U: Ord => std::collections::BTreeSet<U>, std::collections::BTreeSet<U::Output>
);
readable_collection!(
    U: Ord => std::collections::BinaryHeap<U>, std::collections::BinaryHeap<U::Output>
);
pub fn read<T: ReadableFromLine>() -> T::Output {
    let mut line = String::new();
    std::io::stdin().read_line(&mut line).unwrap();
    T::read_line(&line).unwrap()
}
#[macro_export]
macro_rules! read {
    () => {
        let mut line = String::new();
        std::io::stdin().read_line(&mut line).unwrap();
    };
    ( $pat:pat = $t:ty ) => {
        let $pat = read::<$t>();
    };
    ( $( $pat:pat = $t:ty ),+ ) => {
        read!(($($pat),*) = ($($t),*));
    };
}
#[macro_export]
macro_rules! readls {
    ( $( $pat:pat = $t:ty ),+ ) => {
        $(
            read!($pat = $t);
        )*
    };
}
pub fn readx<T: ReadableFromLine>() -> Vec<T::Output> {
    use std::io::{self, BufRead};
    let stdin = io::stdin();
    let result = stdin.lock().lines().map(|line_result| {
        let line = line_result.expect("read from stdin failed");
        T::read_line(&line).unwrap()
    }).collect();
    result
}
#[macro_export]
macro_rules! readx_loop {
    ( |$pat:pat = $t:ty| $body:expr ) => {
        {
            use std::io::BufRead;
            let stdin = std::io::stdin();
            for line in stdin.lock().lines() {
                let line = line.expect("read from stdin failed");
                let $pat = <$t>::read_line(&line).unwrap();
                $body
            }
        }
    };
    ( |$($pat:pat = $t:ty),*| $body:expr ) => {
        readx_loop!(|($($pat),*) = ($($t),*)| $body);
    };
}
pub fn readn<T: ReadableFromLine>(n: usize) -> Vec<T::Output> {
    use std::io::{self, BufRead};
    let stdin = io::stdin();
    let result: Vec<T::Output> = stdin.lock().lines().take(n).map(|line_result| {
        let line = line_result.expect("read from stdin failed");
        T::read_line(&line).unwrap()
    }).collect();
    if result.len() < n {
        panic!("expected reading {} lines, but only {} lines are read",
               n, result.len());
    }
    result
}
#[macro_export]
macro_rules! readn_loop {
    ( $n:expr, |$pat:pat = $t:ty| $body:expr ) => {
        {
            use std::io::BufRead;
            let stdin = std::io::stdin();
            let mut lock = stdin.lock();
            for _ in 0..$n {
                let mut line = String::new();
                lock.read_line(&mut line).expect("read from stdin failed");
                let $pat = <$t>::read_line(&line).unwrap();
                $body
            }
        }
    };
    ( $n:expr, |$($pat:pat = $t:ty),*| $body:expr ) => {
        readn_loop!($n, |($($pat),*) = ($($t),*)| $body);
    };
}
pub trait Words {
    fn read<T: Readable>(&self) -> T::Output;
}
impl<'a> Words for [&'a str] {
    fn read<T: Readable>(&self) -> T::Output {
        T::read_words(self).unwrap()
    }
}
impl<'a> Words for &'a str {
    fn read<T: Readable>(&self) -> T::Output {
        T::read_words(&[self]).unwrap()
    }
}

/*
fn knapsack(len: usize, t: u16, foods: &[(u16, u32)], memo: mut Vec<Vec<Option<(BitSet, u32)>>>) -> (BitSet, u32) {
    memo[len][t as usize].clone().unwrap_or_else(|| {
        let ans = if len == 0 {
            (BitSet::new(foods.len()), 0)
        } else {
            let (t_i, v_i) = foods[len-1];
            if t_i > t {
                knapsack(len-1, t, foods, memo)
            } else {
                let (s1, v1) = knapsack(len-1, t, foods, memo);
                let (mut s2, v2) = knapsack(len-1, t-t_i, foods, memo);
                if v1 >= v2+v_i {
                    (s1, v1)
                } else {
                    *s2.at(len-1) = true;
                    (s2, v2 + v_i)
                }
            }
        };
        memo[len][t as usize] = Some(ans.clone());
        ans
    })
}
 */

use std::cmp;

fn main() {
    read!(n = usize, t_max = usize);
    let foods = readx::<(usize, usize)>();
    let mut dp = vec![vec![(false, 0); t_max]; n+1];
    for i in 0..n {
        let (t_i, v_i) = foods[i];
        for t in 0..cmp::min(t_i, t_max) {
            dp[i+1][t] = (false, dp[i][t].1);
        }
        for t in cmp::min(t_i, t_max)..t_max {
            let v1 = dp[i][t].1;
            let v2 = dp[i][t-t_i].1;
            if v1 >= v2+v_i {
                dp[i+1][t] = (false, v1)
            } else {
                dp[i+1][t] = (true, v2+v_i);
            }
        }
    }
    let mut set = vec![false; n];
    let mut t = t_max-1;
    for i in (1..n+1).rev() {
        if dp[i][t].0 {
            set[i-1] = true;
            t -= foods[i-1].0;
        }
    }
    let val = dp[n][t_max-1].1;
    let ans = val + (0..n).filter(|&i| !set[i]).map(|i| foods[i].1).max().unwrap_or(0);
    println!("{}", ans);
}


Measured memory_bytes: 166140