// Arup Guha
// 6/7/2016
// Solution to 2016 NAIPC Problem H: Jewel Thief

import java.util.*;
import java.io.*;

public class h {
	
    public static int n;
    public static int k;
    public static long[] dp;

    public static void main(String[] args) throws Exception {
	
        // Get basic parameters.
        BufferedReader stdin = new BufferedReader(new InputStreamReader(System.in));
        StringTokenizer tok = new StringTokenizer(stdin.readLine());
        n = Integer.parseInt(tok.nextToken());
        k = Integer.parseInt(tok.nextToken());
	
        // Just store values for each jewel of weight i in jewels[i].
        ArrayList[] jewels = new ArrayList[301];
        for (int i=0; i<jewels.length; i++)
            jewels[i] = new ArrayList<Long>();
		
        // Put in all of the jewels in their appropriate spot.
        for (int i=0; i<n; i++) {
            tok = new StringTokenizer(stdin.readLine());
            int size = Integer.parseInt(tok.nextToken());
            long val = Long.parseLong(tok.nextToken());
            jewels[size].add(val);
        }
	
        // Sort from largest to smallest value for each weight.
        for (int i=0; i<jewels.length; i++) {
            Collections.sort(jewels[i]);
            Collections.reverse(jewels[i]);
        }
	
        // Update each list to store cumulative frequencies.
        for (int i=0; i<jewels.length; i++) {
            for (int j=1; j<jewels[i].size(); j++)
                jewels[i].set(j, ((ArrayList<Long>)jewels[i]).get(j-1)+((ArrayList<Long>)jewels[i]).get(j));
        }
		
        dp = new long[k+1];
        
        // Run the DP going through each list by weight.
        for (int i=1; i<jewels.length; i++) {
            if (jewels[i].size() == 0) continue;
			
            // Only go back one item, since we'll update all offsets of i from
            // our starting spot.
            for (int j=k; j>=i&&j>k-i; j--) {
				
                int rep = jewels[i].size();
                ArrayDeque<range> deq = new ArrayDeque<range>();
				
                // Set up our queue, considering using all items of this size to update index j.
                for (int z=j-i,cnt=0; cnt<rep&&z>=0; z-=i,cnt++) {
                    
                    // For this item, we figure out whether or not it's always
                    // better than what's at the back of the stack, if so we
                    // keep popping until we find the right place for this item.
                    int maxBeat = z+i;
                    while (deq.size() > 0) {
                        range cur = deq.peekLast();
                        maxBeat = getBeat(z, cur.index, i, jewels[i]);
                        maxBeat = Math.min(maxBeat, j);
                        if (maxBeat < cur.high) break;
                        else deq.pollLast();			
                    }
                    
                    // This indicates that the last item in the stack is the best for indexes low and higher.
                    if (deq.size() > 0) deq.peekLast().low = maxBeat+i;
                    
                    // Our new item is best to build off of, for indexes z+i to maxBeat.
                    deq.offerLast(new range(z, z+i, maxBeat));
                }
		
                // Update this first value dp[j] with the best spot to build off for it.
                range front = deq.peekFirst();
                int bestI = front.index;
                dp[j] = Math.max(dp[j], dp[bestI] + ((ArrayList<Long>)jewels[i]).get((j-bestI)/i-1));
		if (front.low == j) deq.pollFirst();
                
		// Now we go through j-i, j-2*i, j-3*i and so forth.		
                for (int z=j-i; z>=i; z-=i) {

                    // One new item to consider to build off of.
                    if (z - rep*i >= 0) {
                        
                        // Do same thing as before, find right place in queue.
                        int maxBeat = z - rep*i + i;
                        while (deq.size() > 0) {
                            range cur = deq.peekLast();
                            maxBeat = getBeat(z-rep*i, cur.index, i, (ArrayList<Long>)jewels[i]);
                            maxBeat = Math.min(maxBeat, j);
                            if (maxBeat < cur.high) break;
                            else deq.pollLast();
                        }
                        
                        // Update the range for the last item, getting rid of it if necessary.
                        if (deq.size() > 0) {
                            deq.peekLast().low = maxBeat+i;
                            if (maxBeat+i > z) deq.pollFirst();
                        }
                        
                        // Place our current item in the back of the queue.
                        deq.offerLast(new range(z-rep*i, z-rep*i+i, maxBeat));
                    }
                    
                    // Update dp[z] accordingly.
                    front = deq.peekFirst();
                    bestI = front.index;
                    dp[z] = Math.max(dp[z], dp[bestI] + ((ArrayList<Long>)jewels[i]).get((z-bestI)/i-1));
                    
                    // Just in case!
                    if (front.low >= z) deq.pollFirst();
                } // end k loop
            } // end j loop
        } // end i loop
	
        // Output result.
        StringBuilder sb = new StringBuilder();
        for (int i=1; i<k; i++)
            sb.append(dp[i]+" ");
        sb.append(dp[k]);
        System.out.println(sb);
    }

    // Returns the highest index where lowI is a better place to build off of than curI,
    // when our item weight is skip and the cumulative value of those items are in vals.
    public static int getBeat(int lowI, int curI, int skip, ArrayList<Long> vals) {
        
        int rep = vals.size();
        int extra = (curI-lowI)/skip;
        
        // Boundary case, lowI never beats curI.
        if (dp[lowI] + vals.get(extra) <= dp[curI]+vals.get(0)) return curI;
        
        // Other boundary - lowI always beats curI.
        if (dp[lowI] + vals.get(rep-1) > dp[curI] + vals.get(rep-1-extra)) return lowI+rep*skip;
        
        // Run a binary search to find the max multiple of i that we add to lowI
        // to make it still better than building off curI.
        int low = extra+1, high = rep-1;
        while (low < high) {
            int mid = (low+high)/2+1;
            if (dp[lowI] + vals.get(mid-1) > dp[curI] + vals.get(mid-1-extra))
                low = mid;
            else
                high = mid-1;
        }
        
        // We want the actual index in the DP array, which we can build off of lowI and low.
        return lowI + skip*low;
    }
}

class range {
    
    public int index;
    public int low;
    public int high;
    
    public range(int i, int mylow, int myhigh) {
        index = i;
        low = mylow;
        high = myhigh;
    }
    
    public String toString() {
        return index+": "+low+","+high+" ";
    }
}