#include <bits/stdc++.h>
using namespace std;
#define ll long long
#define FOR(i, l, r) for(int i = l; i <= r; i++)
#define FORD(i, l, r) for(int i = l; i >= r; i--)
#define db double
#define ldb long double
#define all_1(x) (x).begin() + 1, (x).end()
#define all(x) (x).begin(), (x).end()
#define ins insert
#define pb push_back
template<typename T>void debug_var(const T& var, const string& name){
cerr << name << ": " << var << "\n";
}
template<typename T>void debug_1d(const T& vt, const string& name){
if(vt.empty()){
cerr << name << " is empty!\n";
return;
}
FOR(i, 0, (int)vt.size() - 1){
cerr << name << "[" << i << "]: " << vt[i] << "\n";
}
}
struct Edge{
int u, v, id;
ll w;
};
struct Data{
int type, which;
ll w;
int id;
};
struct DSU_rollback{
int _n;
vector<int> _par, _rnk, _sz;
struct History{
int u, v, rnk_u, sz_u;
};
stack<History> history;
DSU_rollback(int n = 0){
init(n);
}
void init(int n){
while(!history.empty()) history.pop();
_n = n;
_par.assign(n + 1, 0);
_rnk.assign(n + 1, 0);
_sz.assign(n + 1, 1);
iota(all(_par), 0);
}
int find_u(int u){
return _par[u] == u ? u : find_u(_par[u]);
}
void unite(int u, int v){
u = find_u(u);
v = find_u(v);
if(u == v) return;
if(_rnk[u] < _rnk[v]) swap(u, v);
history.push({u, v, _rnk[u], _sz[u]});
if(_rnk[u] == _rnk[v]) ++_rnk[u];
_sz[u] += _sz[v];
_par[v] = u;
}
void rollback(int target_size){
while((int)history.size() > target_size){
History cur = history.top();
history.pop();
_par[cur.v] = cur.v;
_sz[cur.u] = cur.sz_u;
_rnk[cur.u] = cur.rnk_u;
}
}
};
int n, m, q;
void solve(){
if (!(cin >> n >> m)) return;
vector<Edge> edges(m + 1);
FOR(i, 1, m){
cin >> edges[i].u >> edges[i].v >> edges[i].w;
edges[i].id = i;
}
cin >> q;
vector<Data> queries(q + 1);
FOR(i, 1, q){
cin >> queries[i].type >> queries[i].which >> queries[i].w;
queries[i].id = i;
}
vector<int> ans(q + 1, 0);
int BLOCK = sqrt(m) * 2;
for(int L = 1; L <= q; L += BLOCK){
int R = min(q, L + BLOCK - 1);
vector<int> changed_edges, unchanged_edges, need_ans;
vector<bool> is_changed(m + 1, false);
FOR(i, L, R){
if(queries[i].type == 2) need_ans.pb(i);
else is_changed[queries[i].which] = true;
}
FOR(i, 1, m){
if(is_changed[i]) changed_edges.pb(i);
else unchanged_edges.pb(i);
}
sort(all(need_ans), [&](int a, int b){
return queries[a].w > queries[b].w;
});
sort(all(unchanged_edges), [&](int a, int b){
return edges[a].w > edges[b].w;
});
DSU_rollback dsu(n);
int edge_ptr = 0;
for(int q_idx : need_ans){
while(edge_ptr < (int)unchanged_edges.size() && edges[unchanged_edges[edge_ptr]].w >= queries[q_idx].w){
dsu.unite(edges[unchanged_edges[edge_ptr]].u, edges[unchanged_edges[edge_ptr]].v);
edge_ptr++;
}
int checkpoint = dsu.history.size();
for(int e_idx : changed_edges){
ll weight_now = edges[e_idx].w;
FOR(k, L, q_idx){
if(queries[k].type == 1 && queries[k].which == e_idx){
weight_now = queries[k].w;
}
}
if(weight_now >= queries[q_idx].w){
dsu.unite(edges[e_idx].u, edges[e_idx].v);
}
}
ans[q_idx] = dsu._sz[dsu.find_u(queries[q_idx].which)];
dsu.rollback(checkpoint);
}
FOR(i, L, R){
if(queries[i].type == 1){
edges[queries[i].which].w = queries[i].w;
}
}
}
FOR(i, 1, q) if(queries[i].type == 2) cout << ans[i] << "\n";
}
/*
given a undirected graph with n vertices m edges initally each edges have a weight d_i
q queries
update -> set edges i to x_i
query -> how many vertices we can reach if we start from s_i and this car have weight x_i?
(x_i >= d_i)
cool first thing we noticed... krt and hld aint working
okay let's try sqrt decomp
each block contain 1k queries
okay what do we do nxt?
let's define 2 type of edges
unchanged -> didn't change anything in the entire block
changed -> change atleast 1 time
there are atmost q changed edges
and the rest are unchanged
we can keep changed and use it for the next update
if we sort the car weight decreasing
*/
signed main(){
ios_base::sync_with_stdio(0);
cin.tie(0);
int t = 1;
//cin >> t;
while(t--){
solve();
}
return 0;
}