#include <bits/stdc++.h>
using namespace std;
// Problem: Array of n bits (0 or 1), initially unknown
// Given m queries: XOR of bits in range [l, r] equals v
// Check if queries are consistent (valid assignment exists)
// DSU with parity/XOR tracking between nodes
struct ParityDSU {
vector<int> p; // parent
vector<int> rk; // rank for union by rank
vector<int> xr; // XOR relationship to parent
ParityDSU(int sz = 0) {
init(sz);
}
void init(int sz) {
p.resize(sz);
rk.assign(sz, 0);
xr.assign(sz, 0);
iota(p.begin(), p.end(), 0);
}
// Returns {root, xor_value_from_u_to_root}
// Maintains invariant: value[u] XOR xr[u] = value[parent[u]]
pair<int, int> find(int u) {
if (p[u] == u) {
return {u, 0};
}
// Path compression: update XOR value along path
auto [rt, x] = find(p[u]);
xr[u] ^= x; // Update XOR to root
p[u] = rt;
return {p[u], xr[u]};
}
// Add constraint: value[a] XOR value[b] = val
bool add(int a, int b, int val) {
auto [ra, xa] = find(a); // xa = value[a] XOR value[ra]
auto [rb, xb] = find(b); // xb = value[b] XOR value[rb]
// Already in same component - check consistency
if (ra == rb) {
// value[a] XOR value[b] = (value[a] XOR value[ra]) XOR (value[ra] XOR value[b])
// = xa XOR xb
return (xa ^ xb) == val;
}
// Merge two components with union by rank
if (rk[ra] < rk[rb]) {
swap(ra, rb);
swap(xa, xb);
}
// Make rb point to ra
p[rb] = ra;
// value[rb] XOR xr[rb] should equal value[ra]
// We know: value[a] = value[ra] XOR xa, value[b] = value[rb] XOR xb
// Given: value[a] XOR value[b] = val
// So: (value[ra] XOR xa) XOR (value[rb] XOR xb) = val
// Therefore: value[rb] XOR value[ra] = xa XOR xb XOR val
xr[rb] = xa ^ xb ^ val;
if (rk[ra] == rk[rb]) {
rk[ra]++;
}
return true;
}
};
class Solution {
public:
// Check if parity queries are consistent
bool check(long long n, vector<array<long long, 3>>& qs) {
// Key idea: XOR of range [l, r] = prefix[r+1] XOR prefix[l]
// where prefix[i] = XOR of bits [0, i-1]
// So query [l, r] = v means: prefix[l] XOR prefix[r+1] = v
// Collect all relevant prefix positions
vector<long long> pts;
pts.push_back(0); // Always include prefix[0]
for (auto& q : qs) {
pts.push_back(q[0]); // left endpoint
pts.push_back(q[1] + 1); // right endpoint + 1
}
// Coordinate compression (positions can be up to 10^9)
sort(pts.begin(), pts.end());
pts.erase(unique(pts.begin(), pts.end()), pts.end());
auto get = [&](long long v) {
return (int)(lower_bound(pts.begin(), pts.end(), v) - pts.begin());
};
ParityDSU dsu(pts.size());
// Process each query as constraint: prefix[l] XOR prefix[r+1] = v
for (auto& q : qs) {
int l = get(q[0]);
int r = get(q[1] + 1);
int v = (int)q[2];
// Add constraint between prefix positions
if (!dsu.add(l, r, v)) {
return false; // Contradiction found
}
}
return true; // All constraints consistent
}
};
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
long long n;
int m;
cin >> n >> m;
// Each query: [l, r, v] means XOR of range [l, r] equals v
vector<array<long long, 3>> qs(m);
for (int i = 0; i < m; i++) {
cin >> qs[i][0] >> qs[i][1] >> qs[i][2];
}
Solution sol;
bool ans = sol.check(n, qs);
cout << (ans ? "YES" : "NO") << "\n";
return 0;
}