// iostream is too mainstream
#include <cstdio>
// bitch please
#include <iostream>
#include <algorithm>
#include <cstdlib>
#include <vector>
#include <set>
#include <map>
#include <queue>
#include <stack>
#include <list>
#include <cmath>
#include <iomanip>
#include <time.h>
#define dibs reserve
#define OVER9000 1034567890
#define ALL_THE(CAKE,LIE) for(auto LIE =CAKE.begin(); LIE != CAKE.end(); LIE++)
#define tisic 47
#define soclose 1e-9
#define chocolate win
// so much chocolate
#define patkan 9
#define ff first
#define ss second
#define abs(x) ((x < 0)?-(x):x)
#define uint unsigned int
#define dbl long double
using namespace std;
// mylittledoge
/* Edmonds' blossom algorithm
match[v]: vertex matched to it / -1
finds a tree of alternating paths rooted at R; the parent of v is prev[v] or match[v]
also builds a tree of blossoms
'k, I say blossoms, but each blossom = its base
*/
// based on https://sites.google.com/site/indy256/algo/edmonds_matching
int lca(vector<int> &cur_blossom, vector<int> &match, vector<int> &prev, int u, int v) {
// in the tree of blossoms
// lists blossoms on paths to the root, finds the first common one
set<int> blossoms_visited;
int b =cur_blossom[u];
while(true) {
blossoms_visited.insert(b);
if(match[b] == -1 || prev[match[b]] == -1) break;
b =cur_blossom[prev[match[b]]];}
b =cur_blossom[v];
while(true) {
if(blossoms_visited.find(b) != end(blossoms_visited)) return b;
b =cur_blossom[prev[match[b]]];}
return b;}
int find_aug_path(vector< vector<int> > &G, vector<int> &match, vector<int> &prev, int R) {
int N =G.size();
vector<bool> vis(N,false);
vector<int> cur_blossom(N);
for(int i =0; i < N; i++) cur_blossom[i] =i;
queue<int> q;
q.push(R);
vis[R] =true;
while(!q.empty()) {
ALL_THE(G[q.front()],it) if(*it != match[q.front()]) {
// in the same blossom, already processed
if(cur_blossom[*it] == cur_blossom[q.front()]) continue;
if(prev[*it] == -1 && !(match[*it] != -1 && prev[match[*it]] != -1)) {
if(*it == R) continue;
prev[*it] =q.front();
if(match[*it] == -1) return (*it); // augmenting path to *it
vis[match[*it]] =true;
q.push(match[*it]);
continue;}
if(*it != R && !(match[*it] != -1 && prev[match[*it]] != -1)) continue;
// blossom found
int new_blossom =lca(cur_blossom,match,prev,q.front(),*it);
vector<bool> in_new_blossom(N,false);
int v =q.front(), parent_v =*it;
while(cur_blossom[v] != new_blossom) {
in_new_blossom[cur_blossom[v]] =in_new_blossom[cur_blossom[match[v]]] =true;
prev[v] =parent_v;
parent_v =match[v];
v =prev[match[v]];}
v =*it, parent_v =q.front();
while(cur_blossom[v] != new_blossom) {
in_new_blossom[cur_blossom[v]] =in_new_blossom[cur_blossom[match[v]]] =true;
prev[v] =parent_v;
parent_v =match[v];
v =prev[match[v]];}
for(int i =0; i < N; i++) if(in_new_blossom[cur_blossom[i]]) {
cur_blossom[i] =new_blossom;
if(vis[i]) continue;
vis[i] =true;
q.push(i);}
}
q.pop();}
return -1;}
vector<bool> find_winning(vector< vector<int> > &G, vector<int> &match, vector<int> &prev, int R) {
// find all vertices ending at even length alternating paths
int N =G.size();
vector<bool> is_winning(N,false);
is_winning[R] =true;
vector<bool> vis(N,false);
vector<int> cur_blossom(N);
for(int i =0; i < N; i++) cur_blossom[i] =i;
queue<int> q;
q.push(R);
vis[R] =true;
while(!q.empty()) {
ALL_THE(G[q.front()],it) if(*it != match[q.front()]) {
// in the same blossom, already processed
if(cur_blossom[*it] == cur_blossom[q.front()]) continue;
if(prev[*it] == -1 && !(match[*it] != -1 && prev[match[*it]] != -1)) {
if(*it == R) continue;
prev[*it] =q.front();
vis[match[*it]] =true;
is_winning[match[*it]] =true;
q.push(match[*it]);
continue;}
if(*it != R && !(match[*it] != -1 && prev[match[*it]] != -1)) continue;
// blossom found
int new_blossom =lca(cur_blossom,match,prev,q.front(),*it);
vector<bool> in_new_blossom(N,false);
int v =q.front(), parent_v =*it;
while(cur_blossom[v] != new_blossom) {
in_new_blossom[cur_blossom[v]] =in_new_blossom[cur_blossom[match[v]]] =true;
prev[v] =parent_v;
parent_v =match[v];
v =prev[match[v]];}
v =*it, parent_v =q.front();
while(cur_blossom[v] != new_blossom) {
in_new_blossom[cur_blossom[v]] =in_new_blossom[cur_blossom[match[v]]] =true;
prev[v] =parent_v;
parent_v =match[v];
v =prev[match[v]];}
for(int i =0; i < N; i++) if(in_new_blossom[cur_blossom[i]]) {
cur_blossom[i] =new_blossom;
is_winning[i] =true;
if(vis[i]) continue;
vis[i] =true;
q.push(i);}
}
q.pop();}
return is_winning;}
int main() {
cin.sync_with_stdio(0);
cin.tie(0);
int T;
cin >> T;
for(int t =0; t < T; t++) {
int N,M;
cin >> N >> M;
vector< vector<int> > G(N);
for(int i =0; i < M; i++) {
int a,b;
cin >> a >> b;
G[--a].push_back(--b);
G[b].push_back(a);}
// random maximal matching
vector<int> match(N,-1);
int ans =0, matching_sz =0;
while(true) {
// find all vertices that can matched
vector<int> v;
for(int i =0; i < N; i++) if(match[i] == -1) {
bool m =false; // can it be matched?
ALL_THE(G[i],it) if(match[*it] == -1) m =true;
if(m) v.push_back(i);}
if(v.empty()) break; // is maximal
int x =v[rand()%v.size()];
vector<int> w;
ALL_THE(G[x],it) if(match[*it] == -1) w.push_back(*it);
int y =w[rand()%w.size()];
// match edge x-y
matching_sz++;
match[x] =y;
match[y] =x;}
// make it maximum
for(int i =0; i < N; i++) if(match[i] == -1) {
vector<int> prev(N,-1);
int akt =find_aug_path(G,match,prev,i); // end of the found aug. path
// cout << i << " " << akt << " ";
// for(int j =0; j < N; j++) cout << prev[j] << " ";
// cout << endl;
if(akt == -1) continue;
while(akt != -1) {
int prev_akt =match[prev[akt]];
match[akt] =prev[akt];
match[prev[akt]] =akt;
akt =prev_akt;}
matching_sz++;}
// cout << 2*matching_sz << " " << N << "\n";
vector<bool> is_winning(N,false);
for(int i =0; i < N; i++) if(match[i] == -1) {
vector<int> prev(N,-1);
vector<bool> v =find_winning(G,match,prev,i);
for(int j =0; j < N; j++) if(v[j]) is_winning[j] =true;}
for(int i =0; i < N; i++) if(is_winning[i]) ans++;
cout << ans << "\n";
}
return 0;}
// look at my code
// my code is amazing