Problem:
Problem Statement
"Rooks Game" is a single-player game and uses a chessboard which has $N \times N$ grid and $M$ rook pieces.
A rook moves through any number of unoccupied squares horizontally or vertically. When a rook can attack another rook, it can capture the rook and move to the square which was occupied. Note that, in Rooks Game, we don't distinguish between white and black, in other words, every rook can capture any of other rooks.
Initially, there are $M$ rooks on the board. In each move, a rook captures another rook. The player repeats captures until any rook cannot be captured. There are two types of goal of this game. One is to minimize the number of captured rooks, and the other is to maximize it.
In this problem, you are requested to calculate the minimum and maximum values of the number of captured rooks.
Input
The input consists of a single test case in the format below.
$N$ $M$
$x_{1}$ $y_{1}$
$\vdots$
$x_{M}$ $y_{M}$
The first line contains two integers $N$ and $M$ which are the size of the chessboard and the number of rooks, respectively ($1 \le N, M \le 1000$). Each of the following $M$ lines gives the position of each rook. The $i$-th line with $x_{i}$ and $y_{i}$ means that the $i$-th rook is in the $x_{i}$-th column and $y_{i}$-th row ($1 \le x_{i}, y_{i} \le N$). You can assume any two rooks are not in the same place.
Output
Output the minimum and maximum values of the number of captured rooks separated by a single space.
Examples
Input
Output
8 3
1 1
1 8
8 8
1 2
8 4
1 1
1 8
8 8
8 1
2 3
5 5
1 1
2 2
3 3
4 4
5 5
0 0
100 1
100 100
0 0
10 12
1 2
1 4
1 6
3 6
5 6
10 7
8 7
6 7
4 7
2 7
7 3
9 5
7 8

Submitted solutions:

code_snippet: #include <bits/stdc++.h>
using namespace std;
#define inf 0x3fffffff

const int N=2020;
const int M=200020;

int S,T;

struct Edge{
    int to,next,vol,prf;
}edge[M*2];
int head[N*2];
int tmp;

int n,m;

void add(int a,int b,int c,int d)
{
    ++tmp;
    edge[tmp].to=b;
    edge[tmp].next=head[a];
    edge[tmp].vol=c;
    edge[tmp].prf=d;
    head[a]=tmp;
}

int dis[N];
int pre[N];
int last[N];
int flow[N];
int maxflow,mincost;

queue<int> q;

bool vis[N];

bool spfa()
{
    memset(dis,0x3f3f3f3f,sizeof(dis));
    memset(flow,0x3f3f3f3f,sizeof(flow));
    memset(vis,0,sizeof(vis));
    while(!q.empty()) q.pop();
    
    q.push(S); vis[S]=1; dis[S]=0; pre[T]=-1;
    
    while(!q.empty())
    {
	    int f=q.front();
	    q.pop();
	    vis[f]=0;
	    
	    for(int i=head[f];i!=-1;i=edge[i].next)
		if(edge[i].vol)
		{
		    int t=edge[i].to;
		    if(dis[t]>dis[f]+edge[i].prf)
		    {
			    dis[t]=dis[f]+edge[i].prf;
			    
			    pre[t]=f;
			    last[t]=i;
			    flow[t]=min(flow[f],edge[i].vol);
			    
			    if(!vis[t])
			    {
				    vis[t]=1;
				    q.push(t);
				}
			}
		}
	}
	return pre[T]!=-1;
}


void MCMF()
{
    maxflow = mincost = 0;
    while(spfa())
    {
	    int now=T;
	    maxflow+=flow[T];
	    mincost+=flow[T]*dis[T];
	    while(now!=S)
	    {
		    edge[last[now]].vol-=flow[T];
		    edge[last[now]^1].vol+=flow[T];
		    now=pre[now];
		}
	}
}


bool g[N][N];

struct Node{
    int x,y;
}pa[N];
int fa[N];

int cnt;
int find(int x) { return x == fa[x] ? x : fa[x] = find(fa[x]); }
void Union(int x,int y){
    x = find(x); y = find(y);
    if(x == y) return ;
    -- cnt; fa[y] = x;
}

int main() {
	scanf("%d%d",&n,&m);
	for(int i = 1; i <= m; ++ i){
	    int x,y; scanf("%d%d",&x,&y);
	    g[x][y] = 1; 
	    pa[i].x = x; pa[i].y = y;
	}

	//minimum
	memset(head,-1,sizeof(head));
	tmp=-1;
	S = 0; T = n + n + 1;
	for(int i = 1; i <= n; ++ i){
	    for(int j = 1; j <= n; ++ j){
	        if(g[i][j]){
	           add(i,j + n, 1,1);
	           add(j + n, i,0,-1);
	        }
	    }
	}
	for(int i = 1; i <= n; ++ i){
	    add(S,i,1,0); add(i,S,0,0);
	    add(i + n,T,1,0); add(T,i + n,0,0);
	}
	MCMF();
	printf("%d ", m - maxflow);
	
	//maximum
	for(int i = 1; i <= m; ++ i) fa[i] = i; 
	cnt = m;
	
	for(int i = 1; i <= m; ++ i){
	    for(int j = i + 1; j <= m; ++ j){
	        if(pa[i].x == pa[j].x || pa[i].y == pa[j].y) Union(i,j);
	    }
	}
	
	printf("%d\n",m - cnt);
	
	return 0;
}
accuracy: 1
time_ms: 10
memory_kb: 5408
final_rank: 1

code_snippet: #include <bits/stdc++.h>

const int MAXN = 1e3 + 10, INF = 1e9;

int n, m;
int id[MAXN][MAXN], fa[MAXN];

struct NetWork {
	static const int MAXM = MAXN * MAXN + MAXN * 10;
	int B, E, sum, N,  MaxFlow, MinCost;
	int last[MAXN << 1], vis[MAXN << 1], cur[MAXN << 1], dis[MAXN << 1];
	struct Edge { int link, to, flow, cost; } edge[MAXM << 1];
	inline void Init(int _, int __, int ___) {
		B = _, E = __, N = ___, sum = -1;
		std::fill(last, last + N + 1, -1);
	}
	inline void Add(int u, int v, int f, int c) { edge[++sum] = (Edge) { v, last[u], f, c }, last[u] = sum; }
	inline void AddEdge(int u, int v, int f, int c) { Add(u, v, f, c), Add(v, u, 0, -c); }
	inline int SPFA() {
		std::queue <int> Q;
		std::fill(dis, dis + N + 1, INF), dis[B] = 0, Q.push(B);
		while (!Q.empty()) {
			int x = Q.front(); Q.pop(); vis[x] = 0;
			for (int i = last[x]; ~i; i = edge[i].to) {
				int y = edge[i].link, c = edge[i].cost;
				if (dis[y] > dis[x] + c && edge[i].flow) {
					dis[y] = dis[x] + c;
					if (!vis[y]) vis[y] = 1, Q.push(y);
				}
			}
		}
		return dis[E] != INF;
	}
	int Dfs(int x, int dist) {
		int flown = 0, f;
		if (x == E || !dist) return dist; vis[x] = 1;
		for (int &i = cur[x]; ~i; i = edge[i].to) {
			int y = edge[i].link;
			if (!vis[y] && dis[y] - dis[x] == edge[i].cost && ((f = Dfs(y, std::min(dist, edge[i].flow))) > 0)) {
				edge[i].flow -= f, edge[i ^ 1].flow += f;
				flown += f, dist -= f;
				if (!dist) break;
			}
		}
		vis[x] = 0;
		return flown;
	}
	inline void ZKW() {
		MaxFlow = MinCost = 0;
		while (SPFA()) {
			for (int i = 0; i <= N; ++i) cur[i] = last[i], vis[i] = 0;
			int d = Dfs(B, INF);
			MaxFlow += d, MinCost += d * dis[E];
		}
	}
} G;
int find(int x) { return x == fa[x] ? x : fa[x] = find(fa[x]); }

int main() {
	scanf("%d%d", &n, &m);
	for (int i = 1, x, y; i <= m; ++i) scanf("%d%d", &x, &y), id[x][y] = i;
	for (int i = 1; i <= m; ++i) fa[i] = i;
	int cnt = 0;
	for (int i = 1; i <= n; ++i)
		for (int j = 1, lst = 0; j <= n; ++j) if (id[i][j]) {
			if (lst) {
				int x = find(lst), y = find(id[i][j]);
				if (x != y) fa[y] = x, ++cnt;
			}
			lst = id[i][j];
		}
	for (int j = 1; j <= n; ++j)
		for (int i = 1, lst = 0; i <= n; ++i) if (id[i][j]) {
			if (lst) {
				int x = find(lst), y = find(id[i][j]);
				if (x != y) fa[y] = x, ++cnt;
			}
			lst = id[i][j];
		}
	int S = n * 2 + 1, T = S + 1, N = T;
	G.Init(S, T, N);
	for (int i = 1; i <= n; ++i) G.AddEdge(S, i, 1, 0);
	for (int i = 1; i <= n; ++i) G.AddEdge(i + n, T, 1, 0);
	for (int i = 1; i <= n; ++i)
		for (int j = 1; j <= n; ++j) G.AddEdge(i, j + n, 1, id[i][j] ? 1 : 2);
	G.ZKW();
	printf("%d %d\n", m - (2 * n - G.MinCost), cnt);
	return 0;
}
accuracy: 1
time_ms: 230
memory_kb: 38444
final_rank: 2

code_snippet: //#pragma GCC optimize ("-O3","unroll-loops")
#include<iostream>
#include<string>
#include<algorithm>
#include<vector>
#include<queue>
#include<map>
#include<math.h>
#include<iomanip>
#include<set>
#include<numeric>
#include<cstring>
#include<cstdio>
#include<functional>
#include<bitset>
#include<limits.h>
#include<cassert>
#include<iterator>
#include<complex>
#include<stack>


#define REP(i, n) for(int i = 0;i < n;i++)
#define REPR(i, n) for(int i = n;i >= 0;i--)
#define FOR(i, m, n) for(int i = m;i < n;i++)
#define FORR(i, m, n) for(int i = m;i >= n;i--)
#define SORT(v, n) sort(v, v+n);
#define VSORT(v) sort(v.begin(), v.end());
#define REVERSE(v,n) reverse(v,v+n);
#define VREVERSE(v) reverse(v.begin(), v.end());
#define ll long long
#define pb(a) push_back(a)
#define m0(x) memset(x,0,sizeof(x))
#define print(x) cout<<x<<'\n';
#define pe(x) cout<<x<<" ";
#define lb(v,n) lower_bound(v.begin(), v.end(), n);
#define ub(v,n) upper_bound(v.begin(), v.end(), n);
//#define int long long
#define all(x) (x).begin(), (x).end()
//#define double long double

using namespace std;

template<class T> inline bool chmin(T& a, T b) {
	if (a > b) {
		a = b;
		return true;
	}
	return false;
}
template<class T> inline bool chmax(T& a, T b) {
	if (a < b) {
		a = b;
		return true;
	}
	return false;
}

const int MOD = 1000099253;
const int MOD2 = 1000311461;
const ll INF = 1e17;
const int INT_INF = 1e9;
const double pi = acos(-1);
const double EPS = 1e-10;
typedef pair<int, int>P;
const int MAX = 100010;

//Ford-Fulkerson法で最大流を求める O(F|E|)
const int MAX_V = 2010;
struct edge { int to, cap, rev; };
vector<edge>G[MAX_V];
bool used[MAX_V];

void add_edge(int from, int to, int cap) {
	G[from].push_back({ to, cap, (int)G[to].size() });
	G[to].push_back({ from,0,(int)G[from].size() - 1 });
}
int dfs(int v, int t, int f) {
	if (v == t)return f;
	used[v] = true;
	for (int i = 0; i < G[v].size(); i++) {
		edge &e = G[v][i];
		if (!used[e.to] && e.cap > 0) {
			int d = dfs(e.to, t, min(f, e.cap));
			if (d > 0) {
				e.cap -= d;
				G[e.to][e.rev].cap += d;
				return d;
			}
		}
	}
	return 0;
}
int max_flow(int s, int t) {
	int flow = 0;
	for (;;) {
		memset(used, 0, sizeof(used));
		int f = dfs(s, t, INT_INF);
		if (f == 0) {
			return flow;
		}
		flow += f;
	}
}

int siz[MAX];//錬結成分のサイズ
int par[MAX];//親
int Rank[MAX];//木の深さ

//n要素で初期化
void init(int n) {
	REP(i, n) {
		par[i] = i;
		Rank[i] = 0;
		siz[i] = 1;
	}
}

//木の根を求める
int find(int x) {
	if (par[x] == x) {
		return x;
	}
	else {
		return par[x] = find(par[x]);//経路圧縮(根に直接つなぎなおす)もしつつ
	}
}

//xとyの属する集合を併合
void unite(int x, int y) {
	x = find(x);
	y = find(y);
	if (x == y)return;
	//低いほうを高いほうにつなげる(効率化)
	if (Rank[x] < Rank[y]) {
		par[x] = y;
		siz[x] += siz[y];
		siz[y] = siz[x];
	}
	else {
		siz[y] += siz[x];
		siz[x] = siz[y];
		par[y] = x;
		if (Rank[x] == Rank[y])Rank[x]++;
	}
}

//xとyが同じ集合に属するか否か
bool same(int x, int y) {
	return find(x) == find(y);
}


vector<int>X[1010], Y[1010];
set<int>S[1010];
int x[1010], y[1010];
int ans2 = 0;
bool vis[1010];
void dfs2(int n) {
	vis[n] = true;
	for (auto nex : S[n]) {
		if (vis[nex])continue;
		dfs2(nex);
	}
}
signed main() {
	cin.tie(0);
	ios::sync_with_stdio(false);

	int N, M; cin >> N >> M;
	int s = 0, t = 2*N+1;
	init(M + 1);
	FOR(i, 1, N + 1) {
		add_edge(s, i, 1);
		add_edge(i+N, t, 1);
	}
	REP(i, M) {
		cin >> x[i] >> y[i];
		X[x[i]].pb(i); Y[y[i]].pb(i);
		
		add_edge(x[i], y[i]+N, 1);
		//add_edge(y[i] + N, x[i], 1);
	}
	REP(i, M) {
		REP(j, M) {
			if (i == j)continue;
			if (x[i] == x[j] || y[i] == y[j]) {
				unite(i, j);
				S[i].insert(j);
				S[j].insert(i);
			}
		}
	}
	/*REP(i, M) {
		for (auto v : X[x[i]]) {
			if (v == i)continue;
			S[i].insert(v);
			S[v].insert(i);
		}
		for (auto v : Y[y[i]]) {
			if (v == i)continue;
			S[i].insert(v);
			S[v].insert(i);
		}
	}*/
	int mx = max_flow(s, t);
	int ans1 = M - mx;
	int res2 = 0;
	REP(i, M) {
		if (find(i) == i)res2++;
	}
	/*REP(i, M) {
		if (vis[i])continue;
		res2++;
		dfs2(i);
	}*/
	ans2 = M - res2;
	pe(ans1)print(ans2);
}
accuracy: 1
time_ms: 220
memory_kb: 50284
final_rank: 3