using System;
using System.Collections.Generic;
using System.Linq;
public class Program {
    public static void Main() {
        var inputNums = Console.ReadLine().Split(' ').Select(int.Parse).ToArray();
        int n = inputNums[0];
        int v = inputNums[1];
        List<int> itemsVolume = new List<int>();
        List<int> itemsValue = new List<int>();
        for(int i = 0; i< n; i++)
        {
            List<int> inputs = Console.ReadLine().Split(' ').Select(int.Parse).ToList();
            // itemsVolume[i] = inputs[0];
            // itemsValue[i] = inputs[1];
            itemsVolume.Add(inputs[0]);
            itemsValue.Add(inputs[1]);
        }
        zeroOneBackpack(n,v,itemsVolume,itemsValue,out int r1, out int r2);
        Console.WriteLine(r1);
        Console.WriteLine(r2);
    }
    public static void zeroOneBackpack(int n, int v, List<int> itemsVolume,
                                       List<int> itemValue, out int ans1, out int ans2) {
        // 方案1
        int[,] dp1 = new int[n+1,v+1];//这表示前i个物品,容量j下的最大价值,其实01背包就是把一个容量为j的背包看成了从容量为0到容量为j的j+1个子背包,算出每个子背包能装的最大价值,就可以最终得出容量为j的背包能装的最大价值,所以这个数组的意思就是列出了能装0个到n个物品的,容量为0到v的背包,计算每个背包的dp
        
        //装的物品数量为0的背包的价值一定都为0
        for(int j = 0; j<v+1; j++)
        {
            dp1[0,j] = 0;
        }

        for(int i = 1; i < n+1; i++)
        {
            for(int j =1; j< v+1; j++)
            {
                //不将第i个物品放入,则当前dp为dp1[i-1,j] 
                dp1[i,j] = dp1[i-1,j];//必须用dp1[i,j]来承接,因为当 j < itemsVolume[i-1] 时,应该继承 dp1[i-1,j]
                //如果将第i个物品放入(前提是背包容量足够),此时背包中的剩余容量为j - volumes[i-1],所以剩余容量下的最大价值为dp[i-1][j - volumes[i-1]],所以当前容量下的最大价值为剩余容量下的最大价值+当前放入物品的价值dp[i-1][j - volumes[i-1]] + values[i-1]
                if (j >= itemsVolume[i - 1])
                {
                    long valueIfTakeCurrentItem = dp1[i - 1, j - itemsVolume[i-1]] + itemValue[i-1]; //注意,这里的第i个物品的volume放在itemsVolume中的第i-1个,这和dp的索引有差别,因为dp的0索引代表物品数量为0的背包(value也是同理)
                    //对比放第i个物品和不放第i个物品时背包的值,取最大的座位dp[i,j]的值
                    dp1[i,j] = Math.Max((int)valueIfTakeCurrentItem,dp1[i,j]);
                }
                
            }
        }
        ans1 = 0;//最后取结果就是取能放i个物品的背包中的最大值,因为当然是能放的物品越多背包的值才能越大
        for(int j = 0; j< v+1; j++)
        {
            ans1 = Math.Max(dp1[n,j],ans1);
        }

        //方案2:  方案2 = 方案1 + 不可达状态标记 + 直接取 dp[n][V]
        const int INFS = -1000_000_000;
        int[,] dp2 = new int[n+1,v+1] ;
        // 初始化:只有容量0时可达(相当于容量为0的背包恰好背0装满),其他不可达
        dp2[0,0] = 0;
        for (int j = 1; j < v+1; j++)
        {
            dp2[0,j] = INFS; //
        }

        for(int i = 1; i< n+1; i++)
        {
            for (int j = 1; j < v+1; j++)
            {
                //不放入当前物品,继承上一个状态(上一个状态也有可能是INFS)
                dp2[i,j] = dp2[i-1,j];
                //如果放入当前物品,且放入后的上一个状态(背包剩余容量的dp的状态)不为INFS
                if(j >= itemsVolume[i-1] && dp2[i-1, j- itemsVolume[i-1]] != INFS)
                {
                    int valueIfTakeCurrentItem = itemValue[i-1] + dp2[i-1, j- itemsVolume[i-1]];
                    dp2[i,j] = Math.Max(dp2[i,j],valueIfTakeCurrentItem);
                }

            }
        }

        ans2 = dp2[n,v] == INFS ? 0 :dp2[n,v];
    }
}