#include <iostream>
#include <vector>
using namespace std;
class Solver {
static const int MAXN = 100 + 5;
struct Edge {
int idx;
int v, w;
int last;
} edges[MAXN];
int dp[MAXN][MAXN] = {};
int cnt = 0, head[MAXN];
int N, V;
int root = 0;
void addEdge(int v, int w, int fa, int idx) {
if (fa == -1) fa = 0;
edges[cnt] = {idx, v, w, head[fa]};
head[fa] = cnt++;
}
void dfs(int idx) {
for (int e = head[idx]; e != -1; e = edges[e].last) {
Edge edge = edges[e];
dfs(edge.idx);
for (int i = V; i >= edge.v; i--) dp[edge.idx][i] = dp[edge.idx][i - edge.v] + edge.w;
for (int i = 0; i < edge.v; i++) dp[edge.idx][i] = 0;
for (int i = V; i >= 0; i--)
for (int j = i; j >= 0; j--)
dp[idx][i] = max(dp[idx][i], dp[idx][i - j] + dp[edge.idx][j]);
}
}
public:
Solver() {
cin >> N >> V;
for (int i = 0; i <= N; i++) head[i] = -1;
int v[MAXN], w[MAXN], fa[MAXN];
for (int i = 1; i <= N; i++) {
cin >> v[i] >> w[i] >> fa[i];
if (fa[i] == -1) root = i;
}
for (int i = 1; i <= N; i++) addEdge(v[i], w[i], fa[i], i);
// for (int i = 0; i <= N; i++) {
// cout << "root: " << i << endl;
// for (int e = head[i]; e != -1; e = edges[e].last) {
// cout << edges[e].v << " " << edges[e].w << endl;
// }
// cout << "------------" << endl;
// }
dfs(0);
cout << dp[0][V];
}
};
// 大概思路,看成分组背包问题,dp[i][j] 表示以 i 为根的子树,体积为 j 时的最大价值
// 选的时候,可以把子节点所有的 j 看成一组,N 组里面,组内互斥
/*
建好树以后,从根节点开始,递归算
每次先算完子节点的所有体积对应的最大价值,然后把每个子节点(直接子节点)看成一组,变成分组背包
注意一点,某一个节点选完以后,自己也要选,而且选不了自己的状态没有意义,要变 0
*/
class Solver2 {
static const int MAXN= 100 + 5;
int v[MAXN], w[MAXN];
vector<int> g[MAXN];
int N, V;
int dp[MAXN][MAXN] = {};
// 更好理解的一种转移
// 一开始,默认所有 [v[x], V] 是选了 w[x]的
// 之后算完子节点后,进行枚举时,j 应该在 [V, v[x]] 里,因为 小于 v[x] 的 j 非法
// 而枚举组内物品时,组内物品体积需要在 [0, j - v[x]] 里,如果超出,则说明不能选 v[x],不合法
void dfs(int idx) {
for (int i = v[idx]; i <= V; i++) dp[idx][i] = w[idx];
for (auto sub : g[idx]) {
dfs(sub);
for (int j = V; j >= v[idx]; j--)
for (int k = 0; k <= j - v[idx]; k++)
dp[idx][j] = max(dp[idx][j], dp[idx][j - k] + dp[sub][k]);
}
}
// 原版转移方式,子节点算完以后,在 [V - v[x], 0] 进行枚举,因为在这范围内枚举,才是能选 子节点的状态,而 [v[x], V] 会在子节点选完后,最后选
// 每个节点算完以后,因为只是转移完,还没有把自己加上去,所以得在最后加上根节点
// 加上根节点时,为什么从大到小,从小到大会使用已经加过根节点的状态
// 为什么要把 [0, v[x] - 1] 赋成 0,不这样做会导致从非法状态转移
void dfs2(int x) {
for (auto y : g[x]) {
dfs(y);
for (int j = V - v[x]; j >= 0; j--)
for (int k = j; k >= 0; k--)
dp[x][j] = max(dp[x][j], dp[x][j - k] + dp[y][k]);
}
for (int i = V; i >= v[x]; i--) dp[x][i] = dp[x][i - v[x]] + w[x];
for (int i = 0; i < v[x]; i++) dp[x][i] = 0;
}
public:
void f() {
cin >> N >> V;
int root, fa;
for (int i = 1; i <= N; i++) {
cin >> v[i] >> w[i] >> fa;
if (fa == -1) root = i;
else g[fa].push_back(i);
}
dfs2(root);
cout << dp[root][V] << endl;
for (int i = 0; i <= N; i++) {
for (int j = 0; j <= V; j++)
cout << dp[i][j] << " ";
cout << endl;
}
}
};
int main() {
Solver2 s;
s.f();
}