import * as fs from 'fs' // import * as readline from 'readline' // const rl = readline.createInterface({ input: process.stdin, output: process.stdout }) // const ask = (query: string) => new Promise((resolve) => rl.question(query, resolve)) // // Don't forget `rl.close()`. const INT = Math.floor declare global { interface Array { last(): T | undefined isEmpty(): boolean } } Array.prototype.last = function () { return this.length === 0 ? undefined : this[this.length - 1] } Array.prototype.isEmpty = function () { return this.length === 0 } const less = (a: T, b: T) => (a == b ? 0 : a < b ? -1 : 1) const greater = (a: T, b: T) => (a == b ? 0 : a < b ? 1 : -1) const bigIntMax = (...args: bigint[]) => args.reduce((m, e) => (e > m ? e : m)) const bigIntMin = (...args: bigint[]) => args.reduce((m, e) => (e < m ? e : m)) const bigIntAbs = (arg: bigint) => (arg < 0 ? -arg : arg) declare const stdin: number function read_stdin() { return fs.readFileSync(process.env.NODE_ENV === 'debug' ? stdin : process.stdin.fd, 'utf8') } class Input { readonly inputs: string[] private index = 0 constructor(str?: string) { this.inputs = (str ? str : read_stdin()).split(/\s+/) } number() { return Number(this.inputs[this.index++]) } numbers(n: number) { return this.inputs.slice(this.index, (this.index += n)).map(Number) } bigint() { return BigInt(this.inputs[this.index++]) } bigints(n: number) { return this.inputs.slice(this.index, (this.index += n)).map(BigInt) } word() { return this.inputs[this.index++] } words(n: number) { return this.inputs.slice(this.index, (this.index += n)) } } function array(len: number, init: T): T[] { return Array(len).fill(init) } function array2(h: number, w: number, init: T): T[][] { return array(h, 0).map(() => array(w, init)) } class BipartiteMatching { g: number[][] r: number[] c: number[] visited: boolean[] constructor(h: number, w: number) { this.g = [...Array(h)].map(() => []) this.r = Array(h).fill(-1) this.c = Array(w).fill(-1) this.visited = Array(h) } add(i: number, j: number) { this.g[i].push(j) } dfs(i: number) { if (this.visited[i]) return false this.visited[i] = true for (const j of this.g[i]) { if (this.c[j] === -1) { this.r[i] = j this.c[j] = i return true } } for (const j of this.g[i]) { if (this.dfs(this.c[j])) { this.r[i] = j this.c[j] = i return true } } return false } run() { loop: while (true) { this.visited.fill(false) let updated = false for (let i = 0; i < this.r.length; i++) { if (this.r[i] === -1) updated = this.dfs(i) || updated } if (!updated) break loop } return this.r.length - this.r.reduce((cnt, v) => (v === -1 ? cnt + 1 : cnt), 0) } } function main() { const input = new Input() const H = input.number() const W = input.number() const A: { i: number; j: number }[][] = [...Array(5e5 + 1)].map(() => []) for (let i = 0; i < H; i++) { for (let j = 0; j < W; j++) { const a = input.number() A[a].push({ i, j }) } } let ans = 0 const r_map = array(H, 0) const c_map = array(W, 0) for (let a = 1; a <= 5e5; a++) { if (A[a].isEmpty()) continue const r_set = new Set() const c_set = new Set() for (const { i, j } of A[a]) { r_set.add(i) c_set.add(j) } const R = r_set.size const C = c_set.size const g = new BipartiteMatching(R, C) Array.from(r_set).forEach((r, idx) => (r_map[r] = idx)) Array.from(c_set).forEach((c, idx) => (c_map[c] = idx)) for (const { i, j } of A[a]) { g.add(r_map[i], c_map[j]) } const f = g.run() ans += f } console.log(ans) } main()