#include <iostream>
#include <vector>
#include <set>
#include <map>
#include <algorithm>
using namespace std;
// Struktura przechowująca stan pojedynczej składowej w grafie
struct Component {
vector<int> q; // Kolejka wierzchołków do przetworzenia
vector<int> nodes; // Wierzchołki należące do tej składowej
set<int> keys; // Zabrane klucze
map<int, vector<int>> blocked; // Krawędzie zablokowane: wymagany_klucz -> lista_celów
};
int n, m;
vector<int> r;
vector<vector<pair<int, int>>> adj;
vector<int> parent_node;
vector<int> state; // 0: NIEODWIEDZONE, 1: AKTYWNE (w stosie), 2: ZAKOŃCZONE
vector<bool> is_sink;
vector<int> sz;
vector<bool> expanded;
vector<Component*> comps;
// Struktura Zbiorów Rozłącznych (DSU) z kompresją ścieżki
int find_set(int v) {
if (v == parent_node[v]) return v;
return parent_node[v] = find_set(parent_node[v]);
}
// Funkcja łącząca dwie składowe (Small-To-Large Merging)
int merge_components(int u, int v) {
u = find_set(u);
v = find_set(v);
if (u == v) return u;
int size_u = comps[u]->nodes.size() + comps[u]->keys.size() + comps[u]->blocked.size();
int size_v = comps[v]->nodes.size() + comps[v]->keys.size() + comps[v]->blocked.size();
// Zapewniamy, że dołączamy mniejszy do większego (dla wydajności O(N log^2 N))
if (size_u < size_v) {
swap(u, v);
}
for (int node : comps[v]->nodes) {
comps[u]->nodes.push_back(node);
}
for (int key : comps[v]->keys) {
if (comps[u]->keys.find(key) == comps[u]->keys.end()) {
comps[u]->keys.insert(key);
auto it = comps[u]->blocked.find(key);
if (it != comps[u]->blocked.end()) {
for (int nxt : it->second) comps[u]->q.push_back(nxt);
comps[u]->blocked.erase(it);
}
}
}
for (auto& pair : comps[v]->blocked) {
int key = pair.first;
if (comps[u]->keys.find(key) != comps[u]->keys.end()) {
for (int nxt : pair.second) comps[u]->q.push_back(nxt);
} else {
auto& u_vec = comps[u]->blocked[key];
if (u_vec.empty()) {
u_vec = move(pair.second);
} else {
u_vec.insert(u_vec.end(), pair.second.begin(), pair.second.end());
}
}
}
for (int q_item : comps[v]->q) {
comps[u]->q.push_back(q_item);
}
parent_node[v] = u;
delete comps[v]; // Zwalniamy pamięć
comps[v] = nullptr;
return u;
}
int main() {
// Optymalizacja wejścia/wyjścia
ios_base::sync_with_stdio(false);
cin.tie(NULL);
if (!(cin >> n >> m)) return 0;
r.resize(n);
for (int i = 0; i < n; ++i) cin >> r[i];
adj.resize(n);
for (int i = 0; i < m; ++i) {
int u, v, c;
cin >> u >> v >> c;
adj[u].push_back({v, c});
adj[v].push_back({u, c});
}
parent_node.resize(n);
for (int i = 0; i < n; ++i) parent_node[i] = i;
state.assign(n, 0);
is_sink.assign(n, false);
sz.assign(n, 0);
expanded.assign(n, false);
comps.assign(n, nullptr);
vector<int> active_stack;
for (int i = 0; i < n; ++i) {
if (state[find_set(i)] == 0) {
int root = i;
comps[root] = new Component();
comps[root]->q.push_back(i);
comps[root]->nodes.push_back(i);
state[root] = 1;
active_stack.push_back(root);
while (!active_stack.empty()) {
int curr = active_stack.back();
bool paused = false;
while (!comps[curr]->q.empty()) {
int u = comps[curr]->q.back();
comps[curr]->q.pop_back();
int root_u = find_set(u);
if (root_u != curr) {
if (state[root_u] == 0) {
// Znaleziono nową, nieodwiedzoną składową
comps[curr]->q.push_back(u);
comps[u] = new Component();
comps[u]->q.push_back(u);
comps[u]->nodes.push_back(u);
state[u] = 1;
active_stack.push_back(u);
paused = true;
break;
} else if (state[root_u] == 1) {
// Znaleziono cykl aktywnych składowych
comps[curr]->q.push_back(u);
vector<int> cycle_comps;
while (true) {
int top = active_stack.back();
active_stack.pop_back();
cycle_comps.push_back(top);
if (top == root_u) break;
}
int new_root = cycle_comps[0];
for (size_t k = 1; k < cycle_comps.size(); ++k) {
new_root = merge_components(new_root, cycle_comps[k]);
}
active_stack.push_back(new_root);
paused = true;
break;
} else if (state[root_u] == 2) {
// Znaleziono dojście do przetworzonego grafu (ta składowa nie jest ujściem)
state[curr] = 2;
is_sink[curr] = false;
active_stack.pop_back();
delete comps[curr];
comps[curr] = nullptr;
paused = true;
break;
}
} else {
// Ekspansja własnego węzła
if (!expanded[u]) {
expanded[u] = true;
int key = r[u];
if (comps[curr]->keys.find(key) == comps[curr]->keys.end()) {
comps[curr]->keys.insert(key);
auto it = comps[curr]->blocked.find(key);
if (it != comps[curr]->blocked.end()) {
for (int nxt : it->second) comps[curr]->q.push_back(nxt);
comps[curr]->blocked.erase(it);
}
}
for (auto& edge : adj[u]) {
int v = edge.first;
int req_key = edge.second;
if (comps[curr]->keys.find(req_key) != comps[curr]->keys.end()) {
comps[curr]->q.push_back(v);
} else {
comps[curr]->blocked[req_key].push_back(v);
}
}
}
}
}
if (!paused) {
// Składowa w całości eksplorowana (jest ujściem!)
state[curr] = 2;
is_sink[curr] = true;
sz[curr] = comps[curr]->nodes.size();
active_stack.pop_back();
delete comps[curr];
comps[curr] = nullptr;
}
}
}
}
// Znajdowanie najmniejszego rozmiaru ujścia
int min_p = 1e9;
for (int i = 0; i < n; ++i) {
int root = find_set(i);
if (is_sink[root]) {
min_p = min(min_p, sz[root]);
}
}
// Wypisywanie wyniku zgodnie ze specyfikacją wejścia/wyjścia (n zer/jedynek)
for (int i = 0; i < n; ++i) {
int root = find_set(i);
if (is_sink[root] && sz[root] == min_p) {
cout << 1 << (i == n - 1 ? "" : " ");
} else {
cout << 0 << (i == n - 1 ? "" : " ");
}
}
cout << "\n";
return 0;
}