#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
const int N = 2e5 + 10;
int q[N], f[N], g[N];
int n, m;
/*
用一个单调对列来维护一个区间的最大值
*/
int main() {
cin >> n >> m;
for (int i = 1; i <= n; i++) {
int v, w, s;
cin >> v >> w >> s;
//copy上一层的值
memcpy(g, f, sizeof f);
for (int j = 0; j < v; j++) {
int tt = -1, hh = 0;
for (int k = j; k <= m; k += v) {
if (hh <= tt && k - q[hh] > s * v)hh++;//判断队头元素是否超出窗口范围
//减去偏移量(发现每个窗口的体积依次递加,偏移量个数也依次递加,因此相减来抵消
//队列q来维护每个f的值按依次递减顺序来维护
while (hh <= tt && g[q[tt]] - (q[tt] - j) / v * w <= g[k] - (k - j) / v * w)tt--;
q[++tt] = k;
//对头一定是对最大的。
f[k] = g[q[hh]] + (k - q[hh]) / v * w;
}
}
}
cout << f[m] << endl;
}
#include<bits/stdc++.h>
using namespace std;
using i16 = short;
using i32 = int;
using i64 = long long;
using u16 = unsigned short;
using u32 = unsigned;
using u64 = unsigned long long;
using f128 = long double;
using f64 = double;
using f32 = float;
const int maxn = 1e5 + 10;
const int INF = 1e9;
namespace list {
typedef struct node {
int data;//数据域
node *next; //指针域
} LNode, *LinkList; //LinkList是指向LNode类型数据的指针类型定义
struct lklist {
struct node;
int size;
};
//初始化链表
LinkList init_list() {
LinkList L = new LNode;
if (!L)
return NULL;
L->next = NULL; //指针域置空
return L;
}
//打印链表
void Printf_list(LinkList &L) {
LinkList temp = L->next;
while (temp != NULL) {
cout << temp->data << "->";
temp = temp->next;
}
}
//插入数据(头插)
void create_LinkList(LinkList &L, int n) {
while (n--) {
LinkList s = new LNode;
cin >> s->data;
s->next = L->next;
L->next = s;
}
}
//插入数据(尾插)
void create_LinkList_back(LinkList &L, int n) {
LinkList r = L;
while (n--) {
LinkList s = new LNode;
cin >> s->data;
s->next = NULL;
r->next = s;
r = s;
}
}
}
/*-------------------------------------------------------------*/
namespace segment_tree {
// 节点数据结构
struct node {
int l, r;
int val;
int lazy;
} t[maxn << 2];
// 记录原始数据
int a[maxn];
// 建树
void build(int u, int l, int r) {
t[u].l = l;
t[u].r = r;
t[u].lazy = 0;
if (l == r) {
t[u].val = a[l];
return;
}
int mid = (l + r) >> 1;
build(u << 1, l, mid);
build(u << 1 | 1, mid + 1, r);
t[u].val = t[u << 1].val + t[u << 1 | 1].val;
}
// 更新子节点
void pushdown(int u) {
// 将lazy标记向下传给左右孩子节点
t[u << 1].lazy += t[u].lazy;
t[u << 1 | 1].lazy += t[u].lazy;
// 更新左右孩子节点的值,为lazy标记×孩子节点表示的区间长度,记得减1
t[u << 1].val += t[u].lazy * (t[u << 1].r - t[u << 1].l + 1);
t[u << 1 | 1].val += t[u].lazy * (t[u << 1 | 1].r - t[u << 1 | 1].l + 1);
// 清除该节点的lazy标记,防止重复更新
t[u].lazy = 0;
}
// 更新父节点
void pushup(int u) {
t[u].val = t[u << 1].val + t[u << 1 | 1].val;
}
// 区间修改
void modify(int u, int l, int r, int x) { // x为要求区间修改的值
// 找到子区间,则不需要向下寻找
if (l <= t[u].l && t[u].r <= r) {
t[u].lazy += x;
t[u].val += x * (t[u].r - t[u].l + 1);
return;
}
// 若区间分布在节点表示的区间两侧,则分别查找左右孩子表示的区间
int mid = (t[u].l + t[u].r) >> 1;
// 需要查找孩子节点,要将lazy标记向下传递
pushdown(u);
if (l <= mid) modify(u << 1, l, r, x);
if (mid < r) modify(u << 1 | 1, l, r, x);
// 修改完成后要记得更新父节点的值
pushup(u);
}
// 区间查询
int query(int u, int L, int R) {
// 若不在节点表示的区间内,则找不到
if (t[u].l > R || t[u].r < L) return 0;
// 若节点正好是要查询的子区间,则直接返回该节点的值
if (t[u].l >= L && t[u].r <= R) return t[u].val;
// 若要查找的区间分布在节点表示的区间两侧,则递归分别查找
// 记得传递lazy标记,更新孩子节点的值
pushdown(u);
return query(u << 1, L, R) + query(u << 1 | 1, L, R);
}
}
namespace kmp {
void get_next(string t, int *next) {
int i = 0, j = -1;
next[i] = j;
while (i < (int)t.length()) {
if (j == -1 || t[i] == t[j]) {
++i;
++j;
next[i] = j;
} else {
j = next[j];
}
}
}
//kmp算法,查找成功则返回起始位置,查找失败则返回-1
int KMP(string str, string t) {
int i = 0, j = 0;
int *next = new int[t.length() + 1];
get_next(t, next);
while (i < (int)str.length() && j < (int)t.length()) {
if (j == -1 || str[i] == t[j]) {
++i;
++j;
} else
j = next[j];
}
if (j == (int)t.length()) {
return i - j;
} else
return -1;
}
}
namespace treap {
int na;
int ch[maxn][2];//[i][0]代表i左儿子,[i][1]代表i右儿子
int val[maxn], dat[maxn];
int siz[maxn], cnt[maxn];
int tot, root;
int New(int v) { //新增节点,
val[++tot] = v;//节点赋值
dat[tot] = rand();//随机优先级
siz[tot] = 1;//目前是新建叶子节点,所以子树大小为1
cnt[tot] = 1;//新建节点同理副本数为1
return tot;
}
void pushup(int id) { //和线段树的pushup更新一样
siz[id] = siz[ch[id][0]] + siz[ch[id][1]] + cnt[id];//本节点子树大小 = 左儿子子树大小 + 右儿子子树大小 + 本节点副本数
}
void build() {
root = New(-INF), ch[root][1] = New(INF); //先加入正无穷和负无穷,便于之后操作(貌似不加也行)
pushup(root);//因为INF > -INF,所以是右子树,
}
void Rotate(int &id, int d) { //id是引用传递,d(irection)为旋转方向,0为左旋,1为右旋
int temp = ch[id][d ^ 1];//旋转理解:找个动图看一看就好(或参见其他OIer的blog)
ch[id][d ^ 1] = ch[temp][d];//这里讲一个记忆技巧,这些数据都是被记录后马上修改
ch[temp][d] = id;//所以像“Z”一样
id = temp;//比如这个id,在上一行才被记录过,ch[temp][d]、ch[id][d ^ 1]也是一样的
pushup(ch[id][d]), pushup(id); //旋转以后size会改变,看图就会发现只更新自己和转上来的点,pushup一下,注意先子节点再父节点
}//旋转实质是({在满足BST的性质的基础上比较优先级}通过交换本节点和其某个叶子节点)把链叉开成二叉形状(从而控制深度),可以看图理解一下
void insert(int &id, int v) { //id依然是引用,在新建节点时可以体现
if (!id) {
id = New(v);//若节点为空,则新建一个节点
return ;
}
if (v == val[id])cnt[id]++; //若节点已存在,则副本数++;
else { //要满足BST性质,小于插到左边,大于插到右边
int d = v < val[id] ? 0 : 1;//这个d是方向的意思,按照BST的性质,小于本节点则向左,大于向右
insert(ch[id][d], v); //递归实现
if (dat[id] < dat[ch[id][d]])Rotate(id, d ^ 1); //(参考一下图)与左节点交换右旋,与右节点交换左旋
}
pushup(id);//现在更新一下本节点的信息
}
void Remove(int &id, int v) { //最难de部分了
if (!id)return ; //到这了发现查不到这个节点,该点不存在,直接返回
if (v == val[id]) { //检索到了这个值
if (cnt[id] > 1) {
cnt[id]--, pushup(id); //若副本不止一个,减去一个就好
return ;
}
if (ch[id][0] || ch[id][1]) { //发现只有一个值,且有儿子节点,我们只能把值旋转到底部删除
if (!ch[id][1] || dat[ch[id][0]] > dat[ch[id][1]]) { //当前点被移走之后,会有一个新的点补上来(左儿子或右儿子),按照优先级,优先级大的补上来
Rotate(id, 1), Remove(ch[id][1], v); //我们会发现,右旋是与左儿子交换,当前点变成右节点;左旋则是与右儿子交换,当前点变为左节点
} else Rotate(id, 0), Remove(ch[id][0], v);
pushup(id);
} else id = 0; //发现本节点是叶子节点,直接删除
return ;//这个return对应的是检索到值de所有情况
}
v < val[id] ? Remove(ch[id][0], v) : Remove(ch[id][1], v); //继续BST性质
pushup(id);
}
int get_rank(int id, int v) {
if (!id)return 1; //若查询值不存在,返回;因为最后要减一排除哨兵节点,想要结果为-1这里就返回0
if (v == val[id])return siz[ch[id][0]] + 1; //查询到该值,由BST性质可知:该点左边值都比该点的值(查询值)小,故rank为左儿子大小 + 1
else if (v < val[id])return get_rank(ch[id][0], v); //发现需查询的点在该点左边,往左边递归查询
else return siz[ch[id][0]] + cnt[id] + get_rank(ch[id][1], v); //若查询值大于该点值。说明询问点在当前点的右侧,且此点的值都小于查询值,所以要加上cnt[id]
}
int get_val(int id, int rank) {
if (!id)return INF; //一直向右找找不到,说明是正无穷
if (rank <= siz[ch[id][0]])return get_val(ch[id][0], rank); //左边排名已经大于rank了,说明rank对应的值在左儿子那里
else if (rank <= siz[ch[id][0]] + cnt[id])return val[id]; //上一步排除了在左区间的情况,若是rank在左与中(目前节点)中,则直接返回目前节点(中区间)的值
else return get_val(ch[id][1], rank - siz[ch[id][0]] - cnt[id]); //剩下只能在右区间找了,rank减去左区间大小和中区间,继续递归
}
int get_pre(int v) {
int id = root, pre; //递归不好返回,以循环求解
while (id) { //查到节点不存在为止
if (val[id] < v)pre = val[id], id = ch[id][1]; //满足当前节点比目标小,往当前节点的右侧寻找最优值
else id = ch[id][0];//无论是比目标节点大还是等于目标节点,都不满足前驱条件,应往更小处靠近
}
return pre;
}
int get_next(int v) {
int id = root, next;
while (id) {
if (val[id] > v)next = val[id], id = ch[id][0]; //同理,满足条件向左寻找更小解(也就是最优解)
else id = ch[id][1];//与上方同理
}
return next;
}
}
namespace Scapegoat_Tree {
const double alpha = 0.7;
struct node {
node *l, *r;
int val, size, cnt;
bool deleted;
bool isbad() {
return l->cnt > alpha * cnt + 5 || r->cnt > alpha * cnt + 5;
}
void maintain() {
size = !deleted + l->size + r->size;
cnt = 1 + l->cnt + r->cnt;
}
};
node *null;
void dfs(node *o, vector<node *> &v) {
if (o == null)return;
dfs(o->l, v);
if (!o->deleted)v.push_back(o);
dfs(o->r, v);
if (o->deleted)delete o;
}
node *build(vector<node *> &v, int l, int r) {
if (l >= r)return null;
int mid = (l + r) >> 1;
node *o = v[mid];
o->l = build(v, l, mid);
o->r = build(v, mid + 1, r);
o->maintain();
return o;
}
void rebuild(node *&o) {
vector<node *> v;
dfs(o, v);
o = build(v, 0, v.size());
}
void insert(int x, node *&o) {
if (o == null) {
o = new node;
o->l = o->r = null;
o->deleted = false;
o->size = o->cnt = 1;
o->val = x;
return;
} else {
++o->size;
++o->cnt;
if (x >= o->val)
insert(x, o->r);
else
insert(x, o->l);
if (o->isbad())rebuild(o);
}
}
int rank(node *now, int x) {
int ans = 1;
while (now != null) {
if (now->val >= x)now = now->l;
else {
ans += now->l->size + !now->deleted;
now = now->r;
}
}
return ans;
}
int kth(node *now, int x) {
while (now != null) {
if (!now->deleted && now->l->size + 1 == x)
return now->val;
if (now->l->size >= x)now = now->l;
else {
x -= now->l->size + !now->deleted;
now = now->r;
}
}
}
void erase(node *o, int rk) {
if (!o->deleted && rk == o->l->size + 1) {
o->deleted = 1;
--o->size;
return;
}
--o->size;
if (rk <= o->l->size + !o->deleted)
erase(o->l, rk);
else
erase(o->r, rk - o->l->size - !o->deleted);
}
node *root;
void main() {
null = new node;
root = null;
// int n;
// scanf("%d",&n);
// while(n--)
// {
// int op,x;
// scanf("%d%d",&op,&x);
// if(op==1)insert(x,root);
// if(op==2)erase(root,rank(root,x));
// if(op==3)printf("%d\n",rank(root,x));
// if(op==4)printf("%d\n",kth(root,x));
// if(op==5)printf("%d\n",kth(root,rank(root,x)-1));
// if(op==6)printf("%d\n",kth(root,rank(root,x+1)));
// }
}
}
namespace dijkstra {
typedef pair<int, int> PII;
#define N maxn
int n, m;
int h[N], e[N], ne[N], w[N], idx;
int dist[N];
bool st[N]; // 被确定最短路的点集合
void add(int a, int b, int c) {
e[idx] = b, ne[idx] = h[a], w[idx] = c, h[a] = idx ++;
}
void dijkstra() {
memset(dist, 0x3f, sizeof dist);
dist[1] = 0;
priority_queue<PII, vector<PII>, greater<PII>> heap;
heap.push({0, 1});
while (heap.size()) {
auto t = heap.top();
heap.pop();
int ver = t.second;
if (st[ver]) continue;
st[ver] = true;
for (int i = h[ver]; i != -1; i = ne[i]) {
int j = e[i];
if (dist[j] > dist[ver] + w[i]) { // 邻接边的距离大于用当前点更新的距离时就替换
dist[j] = dist[ver] + w[i];// d[ver] 是当前最小点的距离; w[i] 是当前邻接边的权重
heap.push({dist[j], j});
}
}
}
}
inline void init() {
memset(h, -1, sizeof h);
}
}
namespace spfa {
int n, m;
int h[N], e[N], ne[N], w[N], idx;
int dist[N];
bool st[N];
void add(int a, int b, int c) {
e[idx] = b, ne[idx] = h[a], w[idx] = c, h[a] = idx ++;
}
void spfa() {
memset(dist, 0x3f, sizeof dist);
dist[1] = 0;
queue<int> q;
q.push(1);
st[1] = true;
while (q.size()) {
int t = q.front();
q.pop();
st[t] = false;
for (int i = h[t]; i != -1; i = ne[i]) {
int j = e[i];
if (dist[j] > dist[t] + w[i]) {
dist[j] = dist[t] + w[i];
if (!st[j]) {
st[j] = true;
q.push(j);
}
}
}
}
}
#undef N
}
namespace gaosi_xiaoyuan_fa {
double map[111][111];
double ans[111];
double eps = 1e-7;
void main() {
int n;
cin >> n;
for (int i = 1; i <= n; i++)
for (int j = 1; j <= n + 1; j++)
scanf("%lf", &map[i][j]);
for (int i = 1; i <= n; i++) {
int r = i;
for (int j = i + 1; j <= n; j++)
if (fabs(map[r][i]) < fabs(map[j][i]))
r = j; //find_the_biggest_number_of_the_first_column(at present)
if (fabs(map[r][i]) < eps) {
printf("No Solution");
return ;
}
if (i != r)swap(map[i], map[r]); //对换一行或一列,属于找最大当前系数的其中一步。(这样就可以只处理当前行的系数啦!)
double div = map[i][i];
for (int j = i; j <= n + 1; j++)
map[i][j] /= div;
for (int j = i + 1; j <= n; j++) {
div = map[j][i];
for (int k = i; k <= n + 1; k++)
map[j][k] -= map[i][k] * div;
}
}
ans[n] = map[n][n + 1];
for (int i = n - 1; i >= 1; i--) {
ans[i] = map[i][n + 1];
for (int j = i + 1; j <= n; j++)
ans[i] -= (map[i][j] * ans[j]);
}//回带操作
for (int i = 1; i <= n; i++)
printf("%.2lf\n", ans[i]);
}
}
//希尔排序
//不稳定排序
void ShellSort(int *item, int n) {
int gap = n / 2;
while (gap >= 1) {
int t = gap;
int pos = 0;
while (t--) {
for (int i = pos; i < n; i += gap) {
for (int k = pos; k < n - gap; k += gap) {
if (item[k] <= item[k + gap]) {
int m = item[k];
item[k] = item[k + gap];
item[k + gap] = m;
}
}
}
pos++;
}
//每趟排序完输出一次结果
for (int i = 0; i < n; i++) {
cout << item[i] << " ";
}
cout << endl;
gap = gap / 2;
}
}
void SelectSort(int *item, int n) {
int minv, maxv;
for (int i = 0, j = n; i < j; ++i, --j) {
minv = i;
maxv = j;
for (int k = i + 1; k < n; k++) {
if (item[k] < item[minv])minv = k;
if (item[k] > item[maxv])maxv = k;
}
swap(item[i], item[minv]);
swap(item[j], item[maxv]);
}
}
int getPivotkey(int *item, int low, int high) {
int key = item[low];
while (low < high) {
while (low < high && item[high] >= key) {
high--;
}
if (low < high) {
item[low] = item[high];
}
while (low < high && item[low] <= key) {
low++;
}
if (low < high) {
item[high] = item[low];
}
}
item[low] = key;
return low;
}
//快速排序
//不稳定排序
void QuickSort(int *item, int low, int high) {
if (high - low <= 32)SelectSort(item + low, high - low + 1);
if (low <= high) {
int pivotkey = getPivotkey(item, low, high);
cout << item[pivotkey] << " " << pivotkey + 1 << endl;
QuickSort(item, low, pivotkey - 1);
QuickSort(item, pivotkey + 1, high);
}
}
int main() {
ios::sync_with_stdio(false), cin.tie(nullptr);
return 0;
}