输入:3
输出:
1 2 3
1 3 2
2 1 3
2 3 1
3 1 2
3 2 1
import java.util.*;
public class Main {
static final int N = 10;
static int n;
static int[] path = new int[N];
static boolean[] vis = new boolean[N];
static void dfs(int u) {
if (u == n) {
for (int i = 0; i < n; i++) {
System.out.print(path[i] + " ");
}
System.out.println();
return;
}
for (int i = 1; i <= n; i++) {
if (!vis[i]) {
path[u] = i;
vis[i] = true;
dfs(u + 1);
vis[i] = false;
}
}
}
public static void main(String[] args) {
Scanner in = new Scanner(System.in);
n = in.nextInt();
dfs(0);
}
}
输入:4
输出:
.Q..
...Q
Q...
..Q.
..Q.
Q...
...Q
.Q..
import java.util.*;
public class Main {
static final int N = 20;
static int n;
static char[][] g = new char[N][N];
static int[] col = new int[N];
static int[] dg = new int[N];// 正对角线
static int[] udg = new int[N];// 反对角线
static void dfs(int u) {
if (u == n) {
for (int i = 0; i < n; i++) {
for(int j=0;j<n;j++){
System.out.print(g[i][j]);
}
System.out.println();
}
System.out.println();
return;
}
for (int i = 0; i < n; i++) {
//正对角线:y=x+b--->b=y-x--->b非负,则b=y-x+n
//反对角线:y=-x+b-->b=y+x
if (col[i] == 0 && dg[u + i] == 0 && udg[n - u + i] == 0) {
g[u][i] = 'Q';
col[i] = dg[u + i] = udg[n - u + i] = 1;
dfs(u + 1);
col[i] = dg[u + i] = udg[n - u + i] = 0;
g[u][i] = '.';
}
}
}
public static void main(String[] args) {
Scanner in = new Scanner(System.in);
n = in.nextInt();
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) {
g[i][j] = '.';
}
}
dfs(0);
}
}
重心定义:重心是指树中的一个结点,如果将这个点删除后,剩余各个连通块中点数的最大值最小,那么这个节点被称为树的重心
输入:第一行包含整数 n,表示树的结点数。
接下来 n−1 行,每行包含两个整数 a 和 b,表示点 a 和点 b 之间存在一条边。
输出:一个整数 m,表示将重心删除后,剩余各个连通块中点数的最大值
import java.util.*;
public class Main {
static int N = 100010, M = 2 * N, idx, n;
static int[] h = new int[N];
static int[] e = new int[M];
static int[] ne = new int[M];
static boolean[] st = new boolean[N];
static int ans = N;
static void add(int a, int b) {
e[idx] = b;
ne[idx] = h[a];
h[a] = idx++;
}
static int dfs(int u) {
int res = 0;
st[u] = true;
int sum = 1;
for (int i = h[u]; i != -1; i = ne[i]) {
int j = e[i];
if (!st[j]) {
int s = dfs(j);
res = Math.max(res, s);
sum += s;
}
}
res = Math.max(res, n - sum);
ans = Math.min(res, ans);
return sum;
}
public static void main(String[] args) {
Scanner in = new Scanner(System.in);
n = in.nextInt();
for (int i = 1; i <= n; i++) {
h[i] = -1;
}
for (int i = 0; i < n - 1; i++) {
int a = in.nextInt();
int b = in.nextInt();
add(a, b);
add(b, a);
}
dfs(1);
System.out.println(ans);
}
}
5 5
0 1 0 0 0
0 1 0 1 0
0 0 0 0 0
0 1 1 1 0
0 0 0 1 0
求从坐标(0,0)移动到(n-1,m-1)的最少次数,其图中1表示墙,0可走
import java.util.*;
class Pair {
int x;
int y;
public Pair(int x, int y) {
this.x = x;
this.y = y;
}
}
public class Main {
static final int N = 110;
static int n, m;
static int[][] g = new int[N][N];//记录原数组
static int[][] dis = new int[N][N];//记录每个位置到起点的距离
static int bfs() {
Queue<Pair> queue = new LinkedList<>();
queue.add(new Pair(0, 0));
// 初始化dis
for (int i = 0; i < N; i++) {
for (int j = 0; j < N; j++) {
dis[i][j] = -1;
}
}
dis[0][0] = 0;
int[] dx = { -1, 0, 1, 0 }, dy = { 0, 1, 0, -1 };
while (!queue.isEmpty()) {
Pair t = queue.poll();
for (int i = 0; i < 4; i++) {
int x = t.x + dx[i], y = t.y + dy[i];
if (x >= 0 && x < n && y >= 0 && y < m && g[x][y] == 0 && dis[x][y] == -1) {
dis[x][y] = dis[t.x][t.y] + 1;
queue.add(new Pair(x, y));
}
}
}
return dis[n - 1][m - 1];
}
public static void main(String[] args) {
Scanner in = new Scanner(System.in);
String[]nums=in.nextLine().split(" ");
n = Integer.parseInt(nums[0]);
m = Integer.parseInt(nums[1]);
for (int i = 0; i < n; i++) {
nums=in.nextLine().split(" ");
for (int j = 0; j < m; j++) {
g[i][j] = Integer.parseInt(nums[j]);
}
}
System.out.println(bfs());
}
}
1 2 3 1 2 3 1 2 3 1 2 3
x 4 6 4 x 6 4 5 6 4 5 6
7 5 8 7 5 8 7 x 8 7 8 x
import java.util.*;
public class Main {
static String swap(String s, int i, int j) {
StringBuilder res = new StringBuilder(s);
char tmp = res.charAt(i);
res.setCharAt(i, res.charAt(j));
res.setCharAt(j, tmp);
return res.toString();
}
static int bfs(String start) {
Queue<String> queue = new LinkedList<>();
queue.add(start);
HashMap<String, Integer> map = new HashMap<>();
map.put(start, 0);
String end = "12345678x";
int dx[] = { -1, 0, 1, 0 }, dy[] = { 0, 1, 0, -1 };
while (!queue.isEmpty()) {
String tmp = queue.poll();
int distance = map.get(tmp);
if (tmp.equals(end))
return distance;
// 状态转移
int k = tmp.indexOf('x');
int sx = k / 3, sy = k % 3;
for (int i = 0; i < 4; i++) {
int x = sx + dx[i], y = sy + dy[i];
if (x >= 0 && x < 3 && y >= 0 && y < 3) {
tmp = swap(tmp, k, 3 * x + y);
if (!map.containsKey(tmp)) {
map.put(tmp, distance + 1);
queue.add(tmp);
}
// 还原现场
tmp = swap(tmp, k, 3 * x + y);
}
}
}
return -1;
}
public static void main(String[] args) {
Scanner in = new Scanner(System.in);
String s = in.nextLine().replace(" ", "");
// System.out.println(s);
System.out.println(bfs(s));
}
}
输出1号点到n号点的最短距离
import java.util.*;
public class Main {
static int N = 100010, idx, n, m, hh, tt;
static int[] h = new int[N],e = new int[N],ne = new int[N];
static int[] d = new int[N],q = new int[N];
static void add(int a, int b) {
e[idx] = b;
ne[idx] = h[a];
h[a] = idx++;
}
static int bfs() {
hh = 0;
tt = -1;
d[1] = 0;
q[++tt] = 1;
while (hh <= tt) {
int t = q[hh++];
for (int i = h[t]; i != -1; i = ne[i]) {
int s = e[i];
if (d[s] == -1) {
d[s] = d[t] + 1;
q[++tt] = s;
}
}
}
return d[n];
}
public static void main(String[] args) {
Scanner in = new Scanner(System.in);
n = in.nextInt();
m = in.nextInt();
for (int i = 1; i <= n; i++) {
h[i] = -1;
d[i] = -1;
}
for (int i = 0; i < m; i++) {
int a = in.nextInt();
int b = in.nextInt();
add(a, b);
}
System.out.println(bfs());
}
}
import java.util.*;
public class Main {
static int N = 100010, idx, n, m;
//链表模板
static int[] e = new int[N], ne = new int[N], h = new int[N];
//q表示队列,d表示结点的入度数
static int[] q = new int[N], d = new int[N];
static void add(int a, int b) {
e[idx] = b;
ne[idx] = h[a];
h[a] = idx++;
}
static boolean tpsort() {
int hh = 0, tt = -1;
for (int i = 1; i <= n; i++) {
//将所有入度为0的结点插入队列q中
if (d[i] == 0) {
q[++tt] = i;
}
}
while (hh <= tt) {
int t = q[hh++];
//遍历队列中结点所有相关联的边
for (int i = h[t]; i != -1; i = ne[i]) {
int s = e[i];
//将改边的入度数减1
d[s]--;
if (d[s] == 0) {
q[++tt] = s;
}
}
}
return tt == n - 1;
}
public static void main(String[] args) {
Scanner in = new Scanner(System.in);
n = in.nextInt();
m = in.nextInt();
for (int i = 1; i <= n; i++) {
h[i] = -1;
}
for (int i = 0; i < m; i++) {
int a = in.nextInt();
int b = in.nextInt();
d[b]++;
add(a, b);
}
if (tpsort()) {
for (int i = 0; i < n; i++) {
System.out.print(q[i] + " ");
}
} else {
System.out.println(-1);
}
}
}
不适用于带有负权边的图
稠密图(边多):使用邻接矩阵存储
//朴素
import java.util.*;
public class Main {
static int N = 510, n, m, max = 0x3f3f3f3f;
static int[][] g = new int[N][N];//每个点之间的距离
static int[] dist = new int[N];//每个点到起点的距离
static boolean[] st = new boolean[N];//已经确认最短距离的点
static int dijkstra() {
for (int i = 1; i <= n; i++) {
dist[i] = max;
}
dist[1] = 0;
for (int i = 0; i < n; i++) {
//t用来中转的
int t = -1;
for (int j = 1; j <= n; j++) {
if (!st[j] && (t == -1 || dist[j] < dist[t])) {
t = j;
}
}
st[t] = true;
for (int j = 1; j <= n; j++) {
dist[j] = Math.min(dist[j], dist[t] + g[t][j]);
}
}
return dist[n] == max ? -1 : dist[n];
}
public static void main(String[] args) {
Scanner in = new Scanner(System.in);
n = in.nextInt();
m = in.nextInt();
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= n; j++) {
g[i][j] = max;
}
}
for (int i = 0; i < m; i++) {
int a = in.nextInt();
int b = in.nextInt();
int c = in.nextInt();
g[a][b] = Math.min(g[a][b], c);
}
System.out.println(dijkstra());
}
}
稀疏图(边少):用邻接表存储
//堆优化
import java.util.*;
class Pair {
// 距离
int x;
// 结点
int y;
public Pair(int x, int y) {
this.x = x;
this.y = y;
}
}
public class Main {
static int N = 100010, n, m, max = 0x3f3f3f3f;
static int[] h = new int[N], w = new int[N], e = new int[N], ne = new int[N];
static int idx;
static int[] dist = new int[N];// 每个点到起点的距离
static boolean[] st = new boolean[N];// 已经确认最短距离的点
static void add(int a, int b, int c) {
e[idx] = b;
ne[idx] = h[a];
w[idx] = c;
h[a] = idx++;
}
static int dijkstra() {
//维护当前未在st中标记过且离源点最近的点(小根堆)
PriorityQueue<Pair> queue = new PriorityQueue<Pair>((a, b) -> {
return a.x - b.x;
});
for (int i = 1; i <= n; i++) {
dist[i] = max;
}
dist[1] = 0;
queue.add(new Pair(0, 1));
while (!queue.isEmpty()) {
//1.找到当前未在st中出现过且离源点最近的点
Pair p = queue.poll();
int distance = p.x;
int t = p.y;
if (st[t])
continue;
//2.标记该点
st[t] = true;
//3.用t更新其他点的距离
for (int i = h[t]; i != -1; i = ne[i]) {
int j = e[i];
if (dist[j] > distance + w[i]) {
dist[j] = distance + w[i];
queue.add(new Pair(dist[j], j));
}
}
}
return dist[n] == max ? -1 : dist[n];
}
public static void main(String[] args) {
Scanner in = new Scanner(System.in);
n = in.nextInt();
m = in.nextInt();
for (int i = 1; i <= n; i++) {
h[i] = -1;
}
for (int i = 0; i < m; i++) {
int a = in.nextInt();
int b = in.nextInt();
int c = in.nextInt();
add(a, b, c);
}
System.out.println(dijkstra());
}
}
【有边数限制的最短路】: 请你求出从 1 号点到 n 号点的最多经过 k 条边的最短距离,如果无法从 1 号点走到 n 号点,输出 impossible,图中可能存在重边和自环, 边权可能为负数
import java.util.*;
class Node {
int a, b, c;
public Node(int a, int b, int c) {
this.a = a;
this.b = b;
this.c = c;
}
}
public class Main {
static int n, m, k, N = 510, M = 10010, max = 0x3f3f3f3f;
static int[] dist = new int[N];
static int[] back = new int[N];
static Node[] edgs = new Node[M];
static int bellman_ford() {
Arrays.fill(dist, max);
dist[1] = 0;
for (int i = 0; i < k; i++) {
back = Arrays.copyOf(dist, n + 1);
for (int j = 0; j < m; j++) {
Node edg = edgs[j];
int a = edg.a;
int b = edg.b;
int c = edg.c;
dist[b] = Math.min(dist[b], back[a] + c);
}
}
return dist[n];
}
public static void main(String[] args) {
Scanner in = new Scanner(System.in);
n = in.nextInt();
m = in.nextInt();
k = in.nextInt();
for (int i = 0; i < m; i++) {
int a = in.nextInt();
int b = in.nextInt();
int c = in.nextInt();
edgs[i] = new Node(a, b, c);
}
int t = bellman_ford();
if (t > max / 2) {
System.out.println("impossible");
} else {
System.out.println(t);
}
}
}
改进bellman-Ford,最好可以O(m),最坏还是O(nm),数据保证不存在负权回路
import java.util.*;
public class Main {
static int N = 100010, n, m, max = 0x3f3f3f3f;
static int[] h = new int[N], w = new int[N], e = new int[N], ne = new int[N];
static int idx;
static int[] dist = new int[N];// 每个点到起点的距离
static boolean[] st = new boolean[N];// 已经确认最短距离的点
static int[] q = new int[N];
static void add(int a, int b, int c) {
e[idx] = b;
ne[idx] = h[a];
w[idx] = c;
h[a] = idx++;
}
static int spfa() {
int hh = 0, tt = -1;
for (int i = 1; i <= n; i++) {
dist[i] = max;
}
dist[1] = 0;
q[++tt] = 1;
st[1] = true;
while (hh <= tt) {
int t = q[hh++];
st[t] = false;
for (int i = h[t]; i != -1; i = ne[i]) {
int j = e[i];
if (dist[j] > dist[t] + w[i]) {
dist[j] = dist[t] + w[i];
if (!st[j]) {
q[++tt] = j;
st[j] = true;
}
}
}
}
return dist[n];
}
public static void main(String[] args) {
Scanner in = new Scanner(System.in);
n = in.nextInt();
m = in.nextInt();
for (int i = 1; i <= n; i++) {
h[i] = -1;
}
for (int i = 0; i < m; i++) {
int a = in.nextInt();
int b = in.nextInt();
int c = in.nextInt();
add(a, b, c);
}
int t = spfa();
if (t > max / 2) {
System.out.println("impossible");
} else {
System.out.println(t);
}
}
}
Dijkstra和spfa的区别:
【判断是否存在负环】
import java.util.*;
public class Main{
static int N = 2010,M = 10010,n,m,idx;
static int[] h = new int[N],e = new int[M],ne = new int[M],w = new int[M];
static int[] cnt = new int[N]; //存多少边
static int[] dist = new int[N];//存点到起点的距离
static boolean[] st = new boolean[N];//判断队列中是不是有这个数
public static void add(int a,int b,int c){
e[idx] = b;
w[idx] = c;
ne[idx] = h[a];
h[a] = idx++;
}
public static boolean spfa(){
Queue<Integer> queue = new LinkedList<>();
//Arrays.fill(dist,0x3f3f3f3f);
//可能起点到不了负环,所以将所有的点都加入到对列中去
for(int i = 1 ; i <= n ; i++ ){
queue.offer(i);
st[i] = true;//然后标记所有的点
}
while(!queue.isEmpty()){
int t = queue.poll(); //然后将拿出队头
st[t] = false; //然后标记已经不在队列中
for(int i = h[t] ; i != -1 ; i= ne[i]){ //遍历所有的点
int j = e[i];
//如果j到起点的距离 > 上个点t到起点的距离 + t到j的距离,那么就更新dist[j]
if(dist[j] > dist[t] + w[i]){
dist[j] = dist[t] + w[i];
cnt[j] = cnt[t] + 1; // 每一个次更新就将边加上1
//如果边大于n点数,n个点最多只有n-1条边,如果>= n的话,就至少有一个点出现了两次,则说明出线负环
if(cnt[j] >= n) return true;
//然后判断对列中有没有点j,有则插并标记,无则不插
if(!st[j]){
queue.offer(j);
st[j] = true;
}
}
}
}
return false;
}
public static void main(String[] args){
Scanner scan = new Scanner(System.in);
n = scan.nextInt();
m = scan.nextInt();
Arrays.fill(h,-1);
while(m -- > 0){
int a = scan.nextInt();
int b = scan.nextInt();
int c = scan.nextInt();
add(a,b,c);
}
if(spfa()) System.out.println("Yes");
else System.out.println("No");
}
}
解决多源最短路问题,用于求任意两点之间的最短距离
import java.util.*;
public class Main{
static int N = 210,n,m,k,INF = 0x3f3f3f3f;
static int[][] g = new int[N][N];
public static void floyd(){
for(int k = 1 ; k <= n ; k ++ ){
for(int i = 1 ; i <= n ; i ++ ){
for(int j = 1 ; j <= n ; j ++ ){
g[i][j] = Math.min(g[i][j],g[i][k] + g[k][j]);
}
}
}
}
public static void main(String[] args){
Scanner scan = new Scanner(System.in);
n = scan.nextInt();
m = scan.nextInt();
k = scan.nextInt();
for(int i = 1 ; i <= n ; i ++ ){
for(int j = 1 ; j <= n ; j ++ ){
if(i == j) g[i][j] = 0; //可能存在询问自身到自身的距离,所以需要存0
else g[i][j] = INF; //然后其他都可以存成INF最大值
}
}
while(m -- > 0 ){
int a = scan.nextInt();
int b = scan.nextInt();
int c = scan.nextInt();
g[a][b] = Math.min(g[a][b],c); //这里可能存在重边,取最小的边
}
floyd();
while(k -- > 0){
int x = scan.nextInt();
int y = scan.nextInt();
int t = g[x][y];
//这里可能最后到不了目标点,但是可能路径上面存在负权边,然后将目标点更新了,所以不是到底== INF
if(t > INF / 2) System.out.println("impossible");
else System.out.println(t);
}
}
}
prim算法(普利姆算法):对图G(V,E)设置集合S,存放已访问的顶点,然后每次从集合V-S中选择与集合S的最短距离最小的一个顶点(记为u),访问并加入集合S。之后,令顶点u为中介点,优化所有从u能到达的顶点v与集合S之间的最短距离。执行n次(n为顶点个数),直到集合S已包含所有顶点。
import java.util.*;
public class Main {
static int N = 510, n, m, max = 0x3f3f3f3f;
static int[][] g = new int[N][N];// 每个点之间的距离
static int[] dist = new int[N];// 每个点到起点的距离
static boolean[] st = new boolean[N];// 已经确认最短距离的点
static int prim() {
for (int i = 1; i <= n; i++) {
dist[i] = max;
}
dist[1] = 0;
int res = 0;
for (int i = 0; i < n; i++) {
//t表示还没有找到数
int t = -1;
for (int j = 1; j <= n; j++) {
if (!st[j] && (t == -1 || dist[j] < dist[t])) {
t = j;
}
}
//表示未连通,结束返回
if (dist[t] == max)
return max;
res += dist[t];
st[t] = true;
for (int j = 1; j <= n; j++) {
if (!st[j])
//区别于dijkstra算法dist[j]+g[t][j],prim是g[t][j],表示的是j点到集合的距离,而不是到起点的距离
dist[j] = Math.min(dist[j], g[t][j]);
}
}
return res;
}
public static void main(String[] args) {
Scanner in = new Scanner(System.in);
n = in.nextInt();
m = in.nextInt();
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= n; j++) {
g[i][j] = max;
}
}
for (int i = 0; i < m; i++) {
int a = in.nextInt();
int b = in.nextInt();
int c = in.nextInt();
g[a][b] = g[b][a] = Math.min(g[a][b], c);
}
int t = prim();
if (t > max / 2) {
System.out.println("impossible");
} else {
System.out.println(t);
}
}
}
克鲁斯卡尔算法思想包括两个部分:首先是带权图G中e条边的权值的排序;其次是判断新选取的边的两个顶点是否属于同一个连通分量,由于初始将所有顶点看成独立的结点,使用并查集进行处理,入边等操作
import java.util.*;
class Edgs implements Comparable<Edgs> {
int a, b, w;
public Edgs(int a, int b, int w) {
this.a = a;
this.b = b;
this.w = w;
}
public int compareTo(Edgs e) {
return Integer.compare(w, e.w);
}
}
public class Main {
static int N = 100010,M=2*N;
static int[] p = new int[N];
static Edgs[] edgs = new Edgs[M];
static int find(int x) {
if (p[x] != x) {
p[x] = find(p[x]);
}
return p[x];
}
public static void main(String[] args) {
Scanner in = new Scanner(System.in);
String[]strs=in.nextLine().split(" ");
int n = Integer.parseInt(strs[0]);
int m = Integer.parseInt(strs[1]);
for (int i = 1; i <= n; i++) {
p[i] = i;
}
for (int i = 0; i < m; i++) {
strs=in.nextLine().split(" ");
int a = Integer.parseInt(strs[0]);
int b = Integer.parseInt(strs[1]);
int w = Integer.parseInt(strs[2]);
edgs[i] = new Edgs(a, b, w);
}
Arrays.sort(edgs, 0, m);
int res = 0, cnt = 0;
for (int i = 0; i < m; i++) {
int a = edgs[i].a, b = edgs[i].b, w = edgs[i].w;
a = find(a);
b = find(b);
//a,b结点还未连通上,进行连通操作
if (a != b) {
p[a] = b;
res += w;
cnt++;
}
}
if (cnt < n - 1) {
System.out.println("impossible");
} else {
System.out.println(res);
}
}
}
二分图也叫二部图,就是顶点集V可分割为两个互不相交的子集,并且图中每条边依附的两个顶点都分属于这两个互不相交的子集,两个子集内的顶点不相邻。
无向图G为二分图的充分必要条件是,G至少有两个顶点,且其所有回路的长度均为偶数,无奇数环
import java.util.*;
public class Main{
static int N = 100010,M = N*2,n,m,idx;
static int[] h = new int[N],e = new int[M],ne = new int[M];
static int[] color = new int[N];
public static void add(int a,int b){
e[idx] = b;
ne[idx] = h[a];
h[a] = idx++;
}
public static boolean dfs(int u,int c){
color[u] = c; //首先将点u染色成c
//然后遍历一下该点的所有边、
for(int i = h[u]; i != -1; i = ne[i]){
int j = e[i];
//如果该点还没有染色
if(color[j] == 0){
//那就将该点进行染色,3-c是因为我们是将染色成1和2,如果是1,那就将对应的染成2,就用3来减法得出
if(!dfs(j,3-c)) return false; //如果染完色之后返回false,那就说明里面含有奇数环,那就返回false
}
//如果该点已经染过颜色吗,然后点的颜色跟我c的颜色是一样的,那就说明存在奇数环,返回false
else if(color[j] == c) return false;
}
//最后如果很顺利的染完色,那就说明没有奇数环,那就返回true;
return true;
}
public static void main(String[] args){
Scanner scan = new Scanner(System.in);
n = scan.nextInt();
m = scan.nextInt();
Arrays.fill(h,-1);
while(m -- > 0){
int a = scan.nextInt();
int b = scan.nextInt();
add(a,b);add(b,a);//因为是无向边,所以双方指向双方的两条边
}
boolean flag = true;
for(int i = 1 ; i <= n ; i ++ ){
//如果该点还没有染色
if(color[i] == 0){
//那就进行染色操作,第一个点可以自行定义1或者2,表示黑白
if(!dfs(i,1)){
flag = false; //如果返回了false,说明有奇数环就将结束,输出No,否则输出Yes
break;
}
}
}
if(flag) System.out.println("Yes");
else System.out.println("No");
}
}
给定一个二分图,左半部分有n1个点(1~n1),右半部分n2个点,二分图共包含m条边,求出二分图的最大匹配数
import java.util.*;
public class Main {
static final int N = 510, M = 100010;
//邻接表存储:h链表头;e结点值;ne结点的next值
static int[] h = new int[N], e = new int[M], ne = new int[M];
static int[] match = new int[N];
static boolean st[] = new boolean[N];
static int idx;
static void add(int a, int b) {
e[idx] = b;
ne[idx] = h[a];
h[a] = idx++;
}
static boolean find(int x) {
for (int i = h[x]; i != -1; i = ne[i]) {
int j = e[i];
if (!st[j]) {
st[j] = true;
if (match[j] == 0 || find(match[j])) {
match[j] = x;
return true;
}
}
}
return false;
}
public static void main(String[] args) {
Scanner in = new Scanner(System.in);
int n1 = in.nextInt();
int n2 = in.nextInt();
int m = in.nextInt();
for (int i = 0; i < N; i++) {
h[i] = -1;
}
while ((m--) > 0) {
int a = in.nextInt();
int b = in.nextInt();
add(a, b);
}
int res = 0;
for (int i = 1; i <= n1; i++) {
for (int j = 0; j < st.length; j++) {
st[j] = false;
}
if (find(i))
res++;
}
System.out.println(res);
}
}