#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 100010, M = 200010;
int n, m;
struct Data
{
int a, b, c, s, res; // s 是单点的贡献(即所有重复数据)
bool operator< (const Data& t) const
{
if (a != t.a) return a < t.a;
if (b != t.b) return b < t.b;
return c < t.c;
}
bool operator== (const Data& t) const
{
return a == t.a && b == t.b && c == t.c;
}
}q[N], w[N];
int tr[M], ans[N]; // 可以用权值线段树替换
int lowbit(int x)
{
return x & -x;
}
void add(int x, int v)
{
for (int i = x; i < M; i += lowbit(i)) tr[i] += v;
}
int query(int x)
{
int res = 0;
for (int i = x; i; i -= lowbit(i)) res += tr[i];
return res;
}
// 归并排序
void merge_sort(int l, int r)
{
if (l >= r) return;
int mid = l + r >> 1;
merge_sort(l, mid), merge_sort(mid + 1, r);
// 双指针统计区间合并时的贡献
int i = l, j = mid + 1, k = 0;
while (i <= mid && j <= r) // 边界
if (q[i].b <= q[j].b) add(q[i].c, q[i].s), w[k ++ ] = q[i ++ ];
else q[j].res += query(q[j].c), w[k ++ ] = q[j ++ ];
// q[i].b > q[j].b,仅当 q[i].a < q[j].a,则统计此时贡献
while (i <= mid) add(q[i].c, q[i].s), w[k ++ ] = q[i ++ ];
// i 余下的仍然要加入树,以便于后面清空树状数组
while (j <= r) q[j].res += query(q[j].c), w[k ++ ] = q[j ++ ];
// 统计贡献,此时都是满足的
for (i = l; i <= mid; i ++ ) add(q[i].c, -q[i].s); // 清空树状数组
for (i = l, j = 0; j < k; i ++, j ++ ) q[i] = w[j];
}
int main()
{
scanf("%d%d", &n, &m);
for (int i = 0; i < n; i ++ )
{
int a, b, c;
scanf("%d%d%d", &a, &b, &c);
q[i] = {a, b, c, 1};
}
sort(q, q + n);
int k = 1;
for (int i = 1; i < n; i ++ )
if (q[i] == q[k - 1]) q[k - 1].s ++ ;
else q[k ++ ] = q[i];
merge_sort(0, k - 1);
for (int i = 0; i < k; i ++ )
ans[q[i].res + q[i].s - 1] += q[i].s;
for (int i = 0; i < n; i ++ ) printf("%d\n", ans[i]);
return 0;
}