#include <iostream>
using namespace std;
// 新增:定义负无穷(表示「无法装满」的非法状态)
const int INF = 0x3f3f3f3f;
struct it {
int v = 0;
int w = 0;
};
const int N = 1e3 + 5;
it a[N];
int f[N][N]; // 保留原变量名,先算res1,再重置算res2
int main() {
int n, V;
cin >> n >> V;
for (int i = 1; i <= n; i++) {
cin >> a[i].v >> a[i].w;
}
/*
考虑「第 i 个物品、第 i+1 个物品、…、第 n 个物品」这部分物品时,恰好装满体积 j 的最大价值。
举个例子:
若 n=3(3 个物品),则:
f[3][j]:只考虑第 3 个物品时,恰好装满 j 的价值;
f[2][j]:考虑第 2、3 个物品时,恰好装满 j 的价值;
f[1][j]:考虑第 1、2、3 个物品(所有物品)时,恰好装满 j 的价值。
*/
// ========== 第一部分:计算res1(不要求装满,原逻辑不变) ==========
//不在意是否装满,只保证物品体积不溢出背包容量
int res1 = 0;
for (int i = n; i >= 1; i--) {
for (int j = 0; j <= V; j++) {
if (j < a[i].v) {
f[i][j] = f[i + 1][j];
} else f[i][j] = max(f[i + 1][j], f[i + 1][j - a[i].v] + a[i].w);
res1 = max(f[i][j], res1);
}
}
// ========== 第二部分:计算res2(恰好装满,重新初始化+状态转移) ==========
// 关键1:重置f数组,初始化「没有物品时(i=n+1)」的状态
// f[n+1][0] = 0(体积0恰好装满,合法);f[n+1][1~V] = -INF(非法)
for (int j = 0; j <= V; j++) {
//设置条件,保证没装满就是非法条件
if (j == 0) f[n + 1][j] = 0;
else f[n + 1][j] = -INF;
}
// 关键2:用「恰好装满」的逻辑重新计算f数组(遍历逻辑和原代码一致)
int res2 = 0;
for (int i = n; i >= 1; i--) {
for (int j = 0; j <= V; j++) {
if (j < a[i].v) {
// 装不下当前物品,继承考虑i+1件物品时的状态(如果该容量在i+1件物品时是非法时,此时也非法)
f[i][j] = f[i + 1][j];
} else {
// 装得下时,只选「合法状态」的最大值(避免把非法状态当0算)
f[i][j] = max(f[i + 1][j], f[i + 1][j - a[i].v] + a[i].w);
}
}
}
// 关键3:最终判断f[1][V]是否合法(不是负无穷才是有效答案)
if (f[1][V] > 0) res2 = f[1][V];
else res2 = 0; // 无法恰好装满,价值为0
// 输出结果
cout << res1 << endl;
cout << res2 << endl;
return 0;
}