/*
分析:
把环缩点,求最小路径覆盖。
果然比赛更激发潜能呀= =,以前很弱的时候见过这个题,
么a掉,今儿个虚拟赛1Y灭之。
2013-05-21
*/
#include"iostream"
#include"cstdio"
#include"stack"
#include"cstring"
using namespace std;
const int N=5005;
const int M=100005;
int n,m;
int match[N],vis[N];
struct Edge{
int u,v,next;
}edge[M],edge_n[M];
int tot,head[N],tot_n,head_n[N];
void add(int a,int b){
edge[tot].u=a;edge[tot].v=b;edge[tot].next=head[a];head[a]=tot++;
}
void add_n(int a,int b){
edge_n[tot_n].u=a;edge_n[tot_n].v=b;edge_n[tot_n].next=head_n[a];head_n[a]=tot_n++;
}
//Tarjan
int index_s,instack[N],DFN[N],LOW[N],belong[N],cnt;
stack<int>st;
void Tarjan(int k)
{
int j,v;
st.push(k);
instack[k]=1;
DFN[k]=LOW[k]=++index_s;
for(j=head[k];j!=-1;j=edge[j].next)
{
v=edge[j].v;
if(instack[v]) LOW[k]=LOW[k]>DFN[v]?DFN[v]:LOW[k];
else if(DFN[v]==-1)
{
Tarjan(v);
LOW[k]=LOW[k]>LOW[v]?LOW[v]:LOW[k];
}
}
if(LOW[k]==DFN[k])
{
do
{
j=st.top();
st.pop();
instack[j]=0;
belong[j]=cnt;
}while(j!=k);
cnt++;
}
}
void build_new_map()
{
int i;
tot_n=0;
memset(head_n,-1,sizeof(head_n));
for(i=0;i<tot;i++)
{
if(belong[edge[i].u]==belong[edge[i].v]) continue;
add_n(belong[edge[i].u],belong[edge[i].v]);
}
}
void build_map()
{
int i;
int a,b;
tot=0;
memset(head,-1,sizeof(head));
for(i=0;i<m;i++)
{
scanf("%d%d",&a,&b);
add(a,b);
}
//缩点
cnt=0; //这个是新的编号
index_s=0;
memset(DFN,-1,sizeof(DFN));
memset(LOW,-1,sizeof(LOW));
memset(instack,0,sizeof(instack));
for(i=1;i<=n;i++) belong[i]=i;
for(i=1;i<=n;i++) if(DFN[i]==-1) Tarjan(i);
//构造新图
build_new_map();
}
int dfs(int k)
{
int j,v;
for(j=head_n[k];j!=-1;j=edge_n[j].next)
{
v=edge_n[j].v;
if(vis[v]) continue;
vis[v]=1;
if(match[v]==-1 || dfs(match[v]))
{
match[v]=k;
return 1;
}
}
return 0;
}
int main()
{
int T;
int i;
cin>>T;
while(T--)
{
scanf("%d%d",&n,&m);
build_map();
int ans=0;
memset(match,-1,sizeof(match));
for(i=0;i<cnt;i++)
{
memset(vis,0,sizeof(vis));
ans+=dfs(i);
}
cout<<cnt-ans<<endl;
}
return 0;
}