Skip to content

Commit 1f7f8c7

Browse files
committed
✨ feat: implement '@algorithm.ts/mcmf'
1 parent bbf2590 commit 1f7f8c7

29 files changed

+657
-0
lines changed

Diff for: .vscode/settings.json

+2
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@
99
"guanghechen",
1010
"lowbit",
1111
"maxflow",
12+
"mcmf",
13+
"mincost",
1214
"mincut",
1315
"nums",
1416
"pinst",

Diff for: packages/mcmf/README.md

+169
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,169 @@
1+
<header>
2+
<h1 align="center">
3+
<a href="https://github.com/guanghechen/algorithm.ts/tree/main/packages/mcmf#readme">@algorithm.ts/mcmf</a>
4+
</h1>
5+
<div align="center">
6+
<a href="https://www.npmjs.com/package/@algorithm.ts/mcmf">
7+
<img
8+
alt="Npm Version"
9+
src="https://img.shields.io/npm/v/@algorithm.ts/mcmf.svg"
10+
/>
11+
</a>
12+
<a href="https://www.npmjs.com/package/@algorithm.ts/mcmf">
13+
<img
14+
alt="Npm Download"
15+
src="https://img.shields.io/npm/dm/@algorithm.ts/mcmf.svg"
16+
/>
17+
</a>
18+
<a href="https://www.npmjs.com/package/@algorithm.ts/mcmf">
19+
<img
20+
alt="Npm License"
21+
src="https://img.shields.io/npm/l/@algorithm.ts/mcmf.svg"
22+
/>
23+
</a>
24+
<a href="#install">
25+
<img
26+
alt="Module Formats: cjs, esm"
27+
src="https://img.shields.io/badge/module_formats-cjs%2C%20esm-green.svg"
28+
/>
29+
</a>
30+
<a href="https://github.com/nodejs/node">
31+
<img
32+
alt="Node.js Version"
33+
src="https://img.shields.io/node/v/@algorithm.ts/mcmf"
34+
/>
35+
</a>
36+
<a href="https://github.com/facebook/jest">
37+
<img
38+
alt="Tested with Jest"
39+
src="https://img.shields.io/badge/tested_with-jest-9c465e.svg"
40+
/>
41+
</a>
42+
<a href="https://github.com/prettier/prettier">
43+
<img
44+
alt="Code Style: prettier"
45+
src="https://img.shields.io/badge/code_style-prettier-ff69b4.svg?style=flat-square"
46+
/>
47+
</a>
48+
</div>
49+
</header>
50+
<br/>
51+
52+
A typescript implementation of the **MCMF (Min Cost Max Flow)** algorithm.
53+
54+
The **MCMF** algorithm is an algorithm for solving network flow problems.
55+
56+
57+
## Install
58+
59+
* npm
60+
61+
```bash
62+
npm install --save @algorithm.ts/mcmf
63+
```
64+
65+
* yarn
66+
67+
```bash
68+
yarn add @algorithm.ts/mcmf
69+
```
70+
71+
## Usage
72+
73+
* Codeforces contest 1082 Problem G (https://codeforces.com/contest/1082/problem/G):
74+
75+
```typescript
76+
import { createMcmf } from '@algorithm.ts/mcmf'
77+
78+
const mcmf = createMcmf()
79+
80+
// The implementation of `io` is omitted.
81+
function solve(io: any): number {
82+
const [n, m] = io.readIntegersOfLine()
83+
84+
const source = 0
85+
const target: number = n + m + 1
86+
mcmf.init(source, target, n + m + 2, n + m * 3)
87+
88+
const nodes: number[] = io.readIntegersOfLine()
89+
for (let i = 0; i < n; ++i) {
90+
const weight: number = nodes[i]
91+
mcmf.addEdge(i + 1, target, weight, 1)
92+
}
93+
94+
let answer = 0
95+
for (let i = 1; i <= m; ++i) {
96+
const [u, v, weight] = io.readIntegersOfLine()
97+
const x = n + i
98+
answer += weight
99+
mcmf.addEdge(source, x, weight, 1)
100+
mcmf.addEdge(x, u, Number.MAX_SAFE_INTEGER, 1)
101+
mcmf.addEdge(x, v, Number.MAX_SAFE_INTEGER, 1)
102+
}
103+
104+
const [mincost, maxflow] = mcmf.minCostMaxFlow()
105+
answer -= maxflow
106+
return answer
107+
}
108+
```
109+
110+
* Codeforces contest 0277 Problem E (https://codeforces.com/contest/277/problem/E):
111+
112+
```typescript
113+
import { createMcmf } from '@algorithm.ts/mcmf'
114+
115+
const mcmf = createMcmf()
116+
117+
// The implementation of `io` is omitted.
118+
function solve(io: any): number {
119+
const [N] = io.readIntegersOfLine()
120+
121+
const vertexes: Vertex[] = new Array(N)
122+
for (let i = 0; i < N; ++i) {
123+
const [x, y] = io.readIntegersOfLine()
124+
vertexes[i] = { x, y }
125+
}
126+
vertexes.sort((p, q) => {
127+
if (p.y === q.y) return p.x - q.x
128+
return q.y - p.y
129+
})
130+
131+
const source = 0
132+
const target: number = N * 2 + 1
133+
mcmf.init(source, target, N * 2 + 2, N * N + 2 * N)
134+
135+
for (let i = 0; i < N; ++i) {
136+
mcmf.addEdge(source, i + 1, 2, 0)
137+
mcmf.addEdge(N + i + 1, target, 1, 0)
138+
for (let j = i + 1; j < N; ++j) {
139+
if (vertexes[i].y === vertexes[j].y) continue
140+
mcmf.addEdge(i + 1, N + j + 1, 1, dist(vertexes[i], vertexes[j]))
141+
}
142+
}
143+
144+
const [mincost, maxflow] = mcmf.minCostMaxFlow()
145+
const answer = maxflow === N - 1 ? mincost : -1
146+
return answer
147+
}
148+
149+
function dist(p: Vertex, q: Vertex): number {
150+
const d = (p.x - q.x) * (p.x - q.x) + (p.y - q.y) * (p.y - q.y)
151+
return Math.sqrt(d)
152+
}
153+
154+
interface Vertex {
155+
x: number
156+
y: number
157+
}
158+
```
159+
160+
## Related
161+
162+
163+
* [@algorithm.ts/dinic](https://github.com/guanghechen/algorithm.ts/tree/main/packages/dinic)
164+
* [@algorithm.ts/isap](https://github.com/guanghechen/algorithm.ts/tree/main/packages/isap)
165+
* [网络流 24 题](https://me.guanghechen.com/post/algorithm/graph/network-flow/24-problems/)
166+
* [网络流基础之最大权闭合图](https://me.guanghechen.com/post/algorithm/graph/network-flow/%E6%9C%80%E5%A4%A7%E6%9D%83%E9%97%AD%E5%90%88%E5%9B%BE/)
167+
168+
169+
[homepage]: https://github.com/guanghechen/algorithm.ts/tree/main/packages/mcmf#readme
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
// Jest Snapshot v1, https://goo.gl/fbAQLP
2+
3+
exports[`codeforces contest 277 e 1`] = `
4+
Array [
5+
"edgeTot",
6+
"dist",
7+
"edges",
8+
"G",
9+
]
10+
`;
11+
12+
exports[`codeforces contest 1082 g 1`] = `
13+
Array [
14+
"edgeTot",
15+
"dist",
16+
"edges",
17+
"G",
18+
]
19+
`;

Diff for: packages/mcmf/__test__/codeforce.spec.ts

+121
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
import fs from 'fs-extra'
2+
import { createReader, loadFixtures, locateFixtures } from 'jest.setup'
3+
import path from 'path'
4+
import { createMcmf } from '../src'
5+
6+
describe('codeforces', function () {
7+
describe('contest 1082', function () {
8+
test('g', function () {
9+
const mcmf = createMcmf()
10+
function solve(content: string): number {
11+
const io = createReader(content)
12+
const [n, m] = io.readIntegersOfLine()
13+
14+
const source = 0
15+
const target: number = n + m + 1
16+
mcmf.init(source, target, n + m + 2, n + m * 3)
17+
18+
const nodes: number[] = io.readIntegersOfLine()
19+
for (let i = 0; i < n; ++i) {
20+
const weight: number = nodes[i]
21+
mcmf.addEdge(i + 1, target, weight, 1)
22+
}
23+
24+
let answer = 0
25+
for (let i = 1; i <= m; ++i) {
26+
const [u, v, weight] = io.readIntegersOfLine()
27+
const x = n + i
28+
answer += weight
29+
mcmf.addEdge(source, x, weight, 1)
30+
mcmf.addEdge(x, u, Number.MAX_SAFE_INTEGER, 1)
31+
mcmf.addEdge(x, v, Number.MAX_SAFE_INTEGER, 1)
32+
}
33+
34+
const [mincost, maxflow] = mcmf.minCostMaxFlow()
35+
answer -= maxflow
36+
return answer
37+
}
38+
39+
const caseDir: string = locateFixtures('codeforces/1082/g')
40+
const filenames: string[] = fs.readdirSync(caseDir).sort()
41+
for (const filename of filenames) {
42+
if (filename.endsWith('.in')) {
43+
const inputFilepath = path.join(caseDir, filename)
44+
const content = loadFixtures(inputFilepath)
45+
const output: number = solve(content)
46+
47+
const answerFilepath = inputFilepath.replace(/\.in$/, '.out')
48+
const answer = Number(loadFixtures(answerFilepath))
49+
// eslint-disable-next-line jest/no-conditional-expect
50+
expect(output).toEqual(answer)
51+
}
52+
}
53+
54+
mcmf.solve(context => expect(Object.keys(context)).toMatchSnapshot())
55+
})
56+
})
57+
58+
describe('contest 277', function () {
59+
test('e', function () {
60+
const mcmf = createMcmf()
61+
62+
const caseDir: string = locateFixtures('codeforces/0277/e')
63+
const filenames: string[] = fs.readdirSync(caseDir).sort()
64+
for (const filename of filenames) {
65+
if (filename.endsWith('.in')) {
66+
const inputFilepath = path.join(caseDir, filename)
67+
const content = loadFixtures(inputFilepath)
68+
const output: number = solve(content)
69+
const answerFilepath = inputFilepath.replace(/\.in$/, '.out')
70+
const answer = Number(loadFixtures(answerFilepath))
71+
// eslint-disable-next-line jest/no-conditional-expect
72+
expect(Math.abs(output - answer)).toBeLessThanOrEqual(1e-6)
73+
}
74+
}
75+
mcmf.solve(context => expect(Object.keys(context)).toMatchSnapshot())
76+
77+
function solve(content: string): number {
78+
const io = createReader(content)
79+
80+
const [N] = io.readIntegersOfLine()
81+
82+
const vertexes: Vertex[] = new Array(N)
83+
for (let i = 0; i < N; ++i) {
84+
const [x, y] = io.readIntegersOfLine()
85+
vertexes[i] = { x, y }
86+
}
87+
vertexes.sort((p, q) => {
88+
if (p.y === q.y) return p.x - q.x
89+
return q.y - p.y
90+
})
91+
92+
const source = 0
93+
const target: number = N * 2 + 1
94+
mcmf.init(source, target, N * 2 + 2, N * N + 2 * N)
95+
96+
for (let i = 0; i < N; ++i) {
97+
mcmf.addEdge(source, i + 1, 2, 0)
98+
mcmf.addEdge(N + i + 1, target, 1, 0)
99+
for (let j = i + 1; j < N; ++j) {
100+
if (vertexes[i].y === vertexes[j].y) continue
101+
mcmf.addEdge(i + 1, N + j + 1, 1, dist(vertexes[i], vertexes[j]))
102+
}
103+
}
104+
105+
const [mincost, maxflow] = mcmf.minCostMaxFlow()
106+
const answer = maxflow === N - 1 ? mincost : -1
107+
return answer
108+
}
109+
110+
function dist(p: Vertex, q: Vertex): number {
111+
const d = (p.x - q.x) * (p.x - q.x) + (p.y - q.y) * (p.y - q.y)
112+
return Math.sqrt(d)
113+
}
114+
115+
interface Vertex {
116+
x: number
117+
y: number
118+
}
119+
})
120+
})
121+
})
+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
3
2+
0 0
3+
1 0
4+
2 1
+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
3.6502815398728847
+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
4
2+
0 0
3+
1 0
4+
2 1
5+
2 0
+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
-1
+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
2
2+
-1000 -1000
3+
1000 1000
+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
2828.42712474619
+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
20
2+
890 -179
3+
-991 -555
4+
874 452
5+
352 312
6+
60 -743
7+
-363 607
8+
164 -961
9+
-607 580
10+
-474 791
11+
-749 -472
12+
-947 739
13+
555 -719
14+
479 269
15+
254 -201
16+
-417 -352
17+
-596 592
18+
-715 433
19+
319 968
20+
-316 -126
21+
915 548
+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
7451.922614948992
+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
20
2+
44 339
3+
226 988
4+
588 298
5+
251 -852
6+
585 551
7+
-916 814
8+
455 412
9+
272 261
10+
-909 -133
11+
466 -923
12+
928 -374
13+
-188 425
14+
598 189
15+
-642 -84
16+
-110 8
17+
-352 866
18+
-766 -803
19+
-593 823
20+
-786 815
21+
-963 943
+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
7938.614004106956
+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
4 5
2+
1 5 2 2
3+
1 3 4
4+
1 4 4
5+
3 4 5
6+
3 2 2
7+
4 2 2
+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
8

0 commit comments

Comments
 (0)