#include <bits/stdc++.h>
using namespace std;

using ll = long long;
const int INF = INT_MAX;//2147483647
const int MOD = (int)1e9 + 7;
const double EPS = 1e-9;
#ifdef LOCAL_ENV
	#define debug(var) std::cout<<#var" = "<<var<<std::endl
#else
	#define debug(var)
#endif
#define p(var)  std::cout<<var<<std::endl
#define PI (acos(-1))
#define rep(i, n)   for(int i=0, i##_length=(n); i< i##_length; ++i)
#define repeq(i, n) for(int i=1, i##_length=(n); i<=i##_length; ++i)
#define all(a) (a).begin(), (a).end()
#define pb push_back
inline double isnatural(double a) { return a>=0 && ceil(a)==floor(a); }
template<typename T> inline T gcd(T a, T b) { return b ? gcd(b,a%b) : a; }
template<typename T> inline T lcm(T a, T b) { return a / gcd(a,  b) * b; }
template<typename T1, typename T2>inline bool chmax(T1 &a, T2 b) { return a < b && (a = b, true); }
template<typename T1, typename T2>inline bool chmin(T1 &a, T2 b) { return a > b && (a = b, true); }
template<typename T1, typename T2> ostream& operator<<(ostream& s, const pair<T1, T2>& p) {return s << "(" << p.first << ", " << p.second << ")";}
template<typename T> ostream& operator<<(ostream& s, const vector<T>& v) {
	for (int i = 0, len = v.size(); i < len; ++i){
		s << v[i]; if (i < len - 1) s << "\t";
	}
	return s;
}
template<typename T> ostream& operator<<(ostream& s, const vector< vector<T> >& vv) {
	for (int i = 0, len = vv.size(); i < len; ++i){
		s << vv[i] << endl;
	}
	return s;
}
template<typename T1, typename T2> ostream& operator<<(ostream& s, const map<T1, T2>& m) {
	s << "{" << endl;
	for (typeof(m.begin()) itr = m.begin(); itr != m.end(); ++itr){
		s << "\t" << (*itr).first << " : " << (*itr).second << endl;
	}
	s << "}" << endl;
	return s;
}

/*-----8<-----8<-----*/

class Item{
public:
	ll value;
	ll weight;
	bool operator<(const Item &another) const
	{
		return ((double)value/weight) < ((double)another.value/another.weight);
	};
};

ll N,W;
vector<Item> items;

vector<bool> used_items;
vector<bool> opt_items;
ll opt_value=0;

//分枝限定法で探索
//index=index-1番目の商品までは選択が終わった、index番目からの商品はまだ調べてない
//value=今のところの価値合計
//weight=今のところの重さ合計
void searchbb(int index,ll value,ll weight){
	//全部調べ終わった
	if(index==N){
		if(opt_value < value){
			opt_items=used_items;
			opt_value=value;
		}
		return;
	}

	//緩和問題を解く
	ll tmp_weight=weight;
	double tmp_value=value;
	for(int i=index;i<N;i++){
		if(tmp_weight+items[i].weight == W){
			//緩和問題の解が元の問題でも実現可能→暫定解として使えるかも
			tmp_value+=items[i].value;
			if(opt_value<tmp_value){
				opt_value=tmp_value;
				opt_items=used_items;
				for(int j=index;j<=i;j++){
					opt_items[j]=true;
				}
			}
			return;
		}else if(tmp_weight+items[i].weight < W){
			tmp_value+=items[i].value;
			tmp_weight+=items[i].weight;
		}else{
			//重さの余りを詰める
			tmp_value+=(double)items[i].value*(W-tmp_weight)/items[i].weight;
			tmp_weight=W;
			break;
		}
	}

	//緩和問題ですら暫定解を超えられない→これ以上調査する意味がない
	if(tmp_value < opt_value){
		return;
	}

	//重量制限が許されれば、index番目を選ぶ場合で探索
	if(weight+items[index].weight <= W){
		used_items[index]=true;
		searchbb(index+1, value+items[index].value, weight+items[index].weight);
	}
	//index番目を選ばない場合で探索
	used_items[index]=false;
	searchbb(index+1, value, weight);
}

int main(){
	cin >> N >> W;
	items=vector<Item>(N,Item());
	rep(i,N){
		Item item;
		cin >> items[i].weight;
		cin >> items[i].value;
	}

	//コスパのよい順に並び替え
	sort(all(items), [](const Item &x, const Item &y) {
		return ((double)x.value/x.weight) > ((double)y.value/y.weight);
	});

	//暫定解として貪欲に選んでみる
	ll tmp_weight=0,tmp_value=0;
	vector<bool> tmp_items(N,false);
	rep(i,N){
		if(tmp_weight+items[i].weight <= W){
			tmp_value+=items[i].value;
			tmp_weight+=items[i].weight;
			tmp_items[i]=true;
		}else{
			break; 
		}
	}
	opt_items=tmp_items;
	opt_value=tmp_value;

	//分枝限定法で探索
	used_items=vector<bool>(N,false);
	searchbb(0,0,0);

	cout << opt_value << endl;

	return 0;
	
}






Measured memory_bytes: 256