Problem:
Score : 400 points

Problem Statement

You have N items and a bag of strength W.
The i-th item has a weight of w_i and a value of v_i.

You will select some of the items and put them in the bag.
Here, the total weight of the selected items needs to be at most W.

Your objective is to maximize the total value of the selected items.

Constraints

1 ≤ N ≤ 100

1 ≤ W ≤ 10^9

1 ≤ w_i ≤ 10^9

For each i = 2,3,...,N, w_1 ≤ w_i ≤ w_1 + 3.

1 ≤ v_i ≤ 10^7

W, each w_i and v_i are integers.

Input

Input is given from Standard Input in the following format:

N W
w_1 v_1
w_2 v_2
:
w_N v_N

Output

Print the maximum possible total value of the selected items.

Sample Input 1

4 6
2 1
3 4
4 10
3 4

Sample Output 1

11

The first and third items should be selected.

Sample Input 2

4 6
2 1
3 7
4 10
3 6

Sample Output 2

13

The second and fourth items should be selected.

Sample Input 3

4 10
1 100
1 100
1 100
1 100

Sample Output 3

400

You can take everything.

Sample Input 4

4 1
10 100
10 100
10 100
10 100

Sample Output 4

0

You can take nothing.

Solution in C++14 (GCC 5.4.1) (judge verdict: Runtime Error)

#include <bits/stdc++.h>
//#include <mylib.h>
using namespace std;
//cin.sync_with_stdio(false);

#define FOR(i,a,b) for(ll i = (a); i < (b); ++i)
#define FOR_EQ(i,a,b) for(ll i = (a); i <= (b); ++i)
#define FOR_RE(i,a,b) for(ll i = (a)-1; i >= (b); --i)
// FOR_RE(i, n, n - m)  nからm回分
#define rep(i,n) FOR(i,0,n)
#define rep_eq(i,n) FOR_EQ(i,0,n)
#define rep_re(i,n) FOR_RE(i,n,0)

#define DEBUG_VARIABLE(variable) cout << #variable << ":" << variable << endl

#define INFTY 1000000000000

#define WHITE 1//（未訪問）
#define GRAY 2//（訪問）未だに訪問していない頂点への辺を持っている。スタックに退避。
#define BLACK 3//（訪問完了）未訪問への頂点への辺を持たない
/*
int M[N][N];// 隣接行列(頂点と頂点が繋がってる、もしくは重みの値)
int color[N]; // その頂点の探索具合
*/
const int TABLEMAX = 510000;
const int MOD = 1000000007;
//二項係数用の各階乗の値とその逆元を記録するための配列．
int fac[TABLEMAX];
int finv[TABLEMAX / 2], inv[TABLEMAX / 2];
/*
//1個上から時計周り
const int ddx[8] = {0, 1, 1, 1, 0, -1, -1, -1};
const int ddy[8] = {1, 1, 0, -1, -1, -1, 0, 1};
//上右下左
const int dx[4] = {0, 1, 0, -1};
const int dy[4] = {-1, 0, 1, 0};
static const int NIL = -1;
*/
typedef unsigned long long ull;
typedef long long ll;
typedef vector<ll> Vector;
typedef vector<Vector> DVector;
typedef vector< pair<ll, ll> > Vec_Pair;
// 関数prime_factorizationで使う。
// 因数分解で使う。firstは数。secondは指数。
vector< pair<ll, ll> > prime_factor;
// 関数get_factor_listで使う。因数の一覧を取得で使う。
Vector factor_list;
// ２つともエラトステネスの篩で使う。
vector<bool> prime; // 素数かどうか判定。
Vector prime_num;   // 素数を記録。

ll n;

template <class X> void printArray(X *array, ll);
template <class X> void printVector(vector<X> &v);
template <class X> void printDimention(vector< vector<X> > &dv);
template <class X> void printMap(X);
template <class X> void printVecPair(vector<X> &v);
// 何個2がかかっているか
ll factor2(ll);
//nCr方式で組み合わせの数を求める
ll combination(int, int);
//nPrで階乗を求める
int factorial(int, int);
//桁数を返す
ll find_digits(ll);
// 各桁の和を計算する関数
int findSumOfDigits(int);
//次数を得る
int degree(int);
//四捨五入
int shishagonyu(double);
//切り上げ
int roundUp(double);
//切り捨て
int roundDown(double);
//四捨五入
int roundOff(double);
//最大公約数(Greatest Common Divisor)
ll gcd (ll, ll);
//最小公倍数(Least Common Multiple)
ll lcm(ll, ll);
//素因数分解
void prime_factorization(ll);
//エラトステネスの篩
void Eratosthenes(int);
//偶奇判定。奇数ならtrue。
bool odd_even(int);
//aのmod mにおける逆元を計算する
long long modinv(long long, long long);
// a^n mod を計算する（二分累乗）
long long modpow(long long, long long, long long);
//テーブルを作る前処理
void COMinit();
//二項係数計算 nCk, 1 <= k <= n <= 10^7
long long COM(ll, ll);
// longlong型で絶対値を帰す。
long long abs_ll(ll);
// 割り算した時に余りが有る時だけ、+1の商を返す。
long long div_one(ll, ll);
// Stringの各桁をvectorの配列に入れる。でかい桁が添字0。
template <class X> void StringtoVec(string, vector<X> &v);
// 因数の一覧を取得する
void get_factor_list(ll);

int main(int argc, char const *argv[])
{
    cin.tie(0);
    ios::sync_with_stdio(false);

    ll N, W;
    cin >> N >> W;
    vector<pair<ll, ll> >   knapsack;

    rep(i, N){
        ll w, v;
        cin >> w >> v;
        knapsack.push_back(make_pair(w, v));
    }
    knapsack.insert(knapsack.begin(), make_pair(0, 0));
    DVector value;
    vector<vector <bool> >  used(N + 1, vector<bool>(W + 1, false));
    value.resize(N + 1);
    rep_eq(i, N)    value[i].resize(W + 1);

    //printVecPair(knapsack);

    FOR_EQ(i, 1, N){
        FOR_EQ(j, 1, W){
            ll from = j - knapsack[i].first;
            if(from < 0){
                value[i][j] = max((ll)0, value[i - 1][j]);
            }else{
                if(!used[i][from]){
                    if(value[i - 1][j] < value[i][from] + knapsack[i].second){
                        value[i][j] = value[i][from] + knapsack[i].second;
                        used[i][j] = true;
                    }else{
                        value[i][j] = value[i - 1][j];
                    }
                }else{
                    value[i][j] = value[i - 1][j];
                }
                
            }
        }
    }

    //printDimention(value);
    /*
    ll res =  0;
    FOR_EQ(j, 1, W){
        res = max(res, value[N][j]);
    }
    cout << res << endl;
    */
    cout << value[N][W] << endl;

    return 0;
}

template<class X> void printArray(X array[], ll n){
    rep(i, n){
        if(i)   cout << " ";
        cout << array[i];
    }
    cout << endl;
}

template<class X> void printVector(vector<X> &v){
    rep(i, (int)v.size()){
        if(i)   cout << " ";
        cout << v[i];
    }
    cout << endl;
}

//二次元配列の表示
template<class X> void printDimention(vector< vector<X> > &dv){
    rep(i, (ll)dv.size()){
        rep(j, (ll)dv[i].size()){
            if(j)   cout << " ";
            cout << dv[i][j];
        }
        cout << endl;
    }
}

//mapの表示
template <class X> void printMap(X mp) {
    for (auto it : mp) {
        cout << it.first << " " << it.second << endl;
    }
}

// 内部にpairを内包したvectorの表示
template <class X> void printVecPair(vector<X> &v){
    rep(i, (int)v.size()){
        cout << v[i].first << " " << v[i].second << endl;
    }
    cout << endl;
}

//numの中に何個2がかかってるか
ll factor2(ll num){
    int cnt = 0;
    while(num % 2 == 0){
        num /= 2;
        cnt++;
    }
    return cnt;
}

//nCr方式で組み合わせの数を求める
ll combination(int n, int r){
    ll up = 1, down = 1;
    // 以下2行はお試し
    ll half = n / 2;
    if(r >= half)   r = n - r;
    ll up_tmp = n, down_tmp = r;

    if(r == 0)  return 1;
    rep(i, r){
        up *= up_tmp;
        up_tmp--;
        down *= down_tmp;
        down_tmp--;
    }

    return up/down;
}



//nPr方式で階乗を求める
int factorial(int n, int r){
    if(n < r){
        cout << "error" << endl;
        return 0;
    }else{
        int res = 1;
        while(r){
            r--;
            res *= n;
            n--;
        }
        return res;
    }
}

//桁数を返す
ll find_digits(ll n){
    int digit = 0;

    while(n){
        n /= 10;
        digit++;
    }
    return digit;
}
// 各桁の和を計算する関数
int findSumOfDigits(int n){
    int sum = 0;
    while (n > 0) { // n が 0 になるまで
        sum += n % 10;
        n /= 10;
    }
    return sum;
}
//次数を得る
int degree(int n){
    int count = 0;
    while(1){
        if(count <= 0)  break;
        count /= 2;
        count++;
    }
    return count;
}

//四捨五入
int shishagonyu( double x )
{
    return (int)( x < 0.0 ? x-0.5 : x+0.5 );
}

//切り上げ
int roundUp(double n){
    if(n >= 0){
        return (int)n + 1;
    }else{
        return (int)n - 1;
    }
}

//切り捨て
int roundDown(double n){
    return (int)n;
}

//四捨五入
int roundOff(double n){
    double decimal = 0;

    decimal = n - (int)n;

    if(decimal >= 0.5 || decimal <= -0.5){
        return roundUp(n);
    }else{
        return roundDown(n);
    }
}

//最大公約数(Greatest Common Divisor)
ll gcd (ll m, ll n){
    //引数に0がある場合は0を返す
    if(m == 0 || n == 0)    return 0;
    
    //ユークリッドの互除法
    while(m != n){
        if(m > n)   m = m - n;
        else    n = n - m;
    }
    return m;
}

//最小公倍数(Least Common Multiple)
ll lcm(ll m, ll n){
    //引数に0がある場合は0を返す
    if(m == 0 || n == 0)    return 0;
    
    //lcm = m * n / gcd(m, n)
    return ((m / gcd(m, n)) * n);
}

//素因数分解 firstは数。secondは指数。
void prime_factorization(ll n){
    int div = 2, res;
    
    bool flg = false;
    if(n < 2){
        cout << "Error" << endl;
        return;
    }
    while(n >= div * div){
        if(n % div == 0){
            res = div;
            //resをグローバルな配列に代入すれば記録できる。
            if(flg){
                prime_factor[prime_factor.size() - 1].second++;
            }else{
                prime_factor.push_back(make_pair(0, 1));
                prime_factor[prime_factor.size() - 1].first = res;
            }
            n /= div;
            flg = true;
        }else{
            div++;
            flg = false;
        }
    }
    //ラス1の素因数
    res = n;
    if(prime_factor[prime_factor.size() - 1].first == res){
        prime_factor[prime_factor.size() - 1].second++;
    }else{
        prime_factor.push_back(make_pair(res, 1));
    }
}

//エラトステネスの篩
void Eratosthenes(int n){
    // prime_bool の添え字と見たい数は一致させたいので、
    // prime_boolやprime_numは１つ多く宣言すること。
    prime[0] = false;
    prime[1] = false;
    
    for(int i = 2; i < sqrt(n); i++){
        if(prime[i]){
            for(int j = 0; i * (j + 2) < n; j++){
                prime[i *(j + 2)] = false;
            }
        }
    }
    // 素数をprime_numに記録
    rep(i, n){
        if(prime[i])    prime_num.push_back(i);
    }
    /*
    int cnt = 0;
    for(int i = 2; i < n; i++){
        if(prime[i]){
            cnt++;
        }
    }
    return cnt;
    */
}
//偶奇判定。奇数ならtrue。
bool odd_even(int num){
    bool judge;
    if(num % 2){
        judge = true;
    }else{
        judge = false;
    }
    return judge;
}
//aのmod mにおける逆元を計算する
long long modinv(long long a, long long m) {
    long long b = m, u = 1, v = 0;
    while (b) {
        long long t = a / b;
        a -= t * b; swap(a, b);
        u -= t * v; swap(u, v);
    }
    u %= m;
    if (u < 0) u += m;
    return u;
}
// a^n mod を計算する
long long modpow(long long a, long long n, long long mod) {
    long long res = 1;
    while (n > 0) {
        if (n & 1) res = res * a % mod;
        a = a * a % mod;
        n >>= 1;
    }
    return res;
}
//main関数内でCOMinitを呼んだ後，COMを呼ぶ．
// テーブルを作る前処理
void COMinit() {
    fac[0] = fac[1] = 1;
    finv[0] = finv[1] = 1;
    inv[1] = 1;
    for (int i = 2; i < TABLEMAX; i++){
        fac[i] = fac[i - 1] * i % MOD;
    }
    for (int i = 2; i < TABLEMAX / 2; i++){
        inv[i] = MOD - inv[MOD % i] * (MOD / i) % MOD;
        finv[i] = finv[i - 1] * inv[i] % MOD;
    }
}
// 二項係数計算 nCk, nCrのcombinationでも使える
long long COM(ll n, ll k){
    if (n < k) return 0;
    if (n < 0 || k < 0) return 0;
    return fac[n] * (finv[k] * finv[n - k] % MOD) % MOD;
}

long long abs_ll(ll a){
    if(a >= 0){
        return a;
    }else{
        return -a;
    }
}

// 割り算した時に余りが有る時だけ、+1の商を返す。
long long div_one(ll res, ll div){
    // res 割られる数、div 割る数
    return (res + div - 1)/div;
}

// Stringの各桁をvectorの配列に入れる。でかい桁が添字0。
template <class X> void StringtoVec(string str, vector<X> &v){
    ll num = stoll(str);
    ll it = str.size() - 1;
    // 桁を逆にしたい時、下をon
    //ll it = 0;
    v.resize(str.size());
    while(num){
        v[it] = num % 10;
        num /= 10;
        it--;
        //it++;
        if(it < 0){
            cout << "reference error" << endl;
            break;
        }
    }
}

// nに数を入れるとその約数の一覧をfactor_listに格納する
void get_factor_list(ll n){
    int div = 1, res;

    while(n >= div * div){
        if(n % div == 0){
            res = div;
            ll tmp = n / res;
            factor_list.push_back(res);
            if(tmp != 1)    factor_list.push_back(tmp);
            div++;
        }else{
            div++;
        }
    }
    sort(factor_list.begin(), factor_list.end());
}

// 簡単な繰返し自乗法
// 再帰を使いながら高速で
ll RepeatSquaring(ll N, ll P, ll M){
    // Nは基数、Pは指数、MはMOD
    if(P == 0)  return 1;
    if(P % 2 == 0){
        ll tmp = RepeatSquaring(N, P/2, M);
        return tmp * tmp % M;
    }else{
        return N * RepeatSquaring(N, P - 1, M) % M;
    }
}