diff --git a/Cargo.lock b/Cargo.lock index 7c3680f..fbb9de7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -159,6 +159,7 @@ dependencies = [ "http-body-util", "hyper-util", "iso8601", + "rand", "serde", "serde_json", "sha2", @@ -199,9 +200,9 @@ checksum = "89e25b6adfb930f02d1981565a6e5d9c547ac15a96606256d3b59040e5cd4ca3" [[package]] name = "bitflags" -version = "2.9.0" +version = "2.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c8214115b7bf84099f1309324e63141d4c5d7cc26862f97a0a857dbefe165bd" +checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967" dependencies = [ "serde", ] @@ -244,9 +245,9 @@ checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" [[package]] name = "cc" -version = "1.2.22" +version = "1.2.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32db95edf998450acc7881c932f94cd9b05c87b4b2599e8bab064753da4acfd1" +checksum = "5f4ac86a9e5bc1e2b3449ab9d7d3a6a405e3d1bb28d7b9be8614f55846ae3766" dependencies = [ "shlex", ] @@ -405,16 +406,6 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" -[[package]] -name = "errno" -version = "0.3.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "976dd42dc7e85965fe702eb8164f21f450704bdde31faefd6471dba214cb594e" -dependencies = [ - "libc", - "windows-sys 0.59.0", -] - [[package]] name = "etcetera" version = "0.8.0" @@ -437,12 +428,6 @@ dependencies = [ "pin-project-lite", ] -[[package]] -name = "fastrand" -version = "2.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" - [[package]] name = "flume" version = "0.11.1" @@ -565,19 +550,7 @@ checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" dependencies = [ "cfg-if", "libc", - "wasi 0.11.0+wasi-snapshot-preview1", -] - -[[package]] -name = "getrandom" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4" -dependencies = [ - "cfg-if", - "libc", - "r-efi", - "wasi 0.14.2+wasi-0.2.4", + "wasi", ] [[package]] @@ -743,9 +716,9 @@ dependencies = [ [[package]] name = "hyper-util" -version = "0.1.11" +version = "0.1.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "497bbc33a26fdd4af9ed9c70d63f61cf56a938375fbb32df34db9b1cd6d643f2" +checksum = "cf9f1e950e0d9d1d3c47184416723cf29c0d1f93bd8cccf37e4beb6b44f31710" dependencies = [ "bytes", "futures-channel", @@ -834,9 +807,9 @@ checksum = "00210d6893afc98edb752b664b8890f0ef174c8adbb8d0be9710fa66fbbf72d3" [[package]] name = "icu_properties" -version = "2.0.0" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2549ca8c7241c82f59c80ba2a6f415d931c5b58d24fb8412caa1a1f02c49139a" +checksum = "016c619c1eeb94efb86809b015c58f479963de65bdb6253345c1a1276f22e32b" dependencies = [ "displaydoc", "icu_collections", @@ -850,9 +823,10 @@ dependencies = [ [[package]] name = "icu_properties_data" -version = "2.0.0" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8197e866e47b68f8f7d95249e172903bec06004b18b2937f1095d40a0c57de04" +checksum = "298459143998310acd25ffe6810ed544932242d3f07083eee1084d83a71bd632" + [[package]] name = "icu_provider" @@ -904,9 +878,9 @@ dependencies = [ [[package]] name = "iso8601" -version = "0.6.2" +version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c5c177cff824ab21a6f41079a4c401241c4e8be14f316c4c6b07d5fca351c98d" +checksum = "e1082f0c48f143442a1ac6122f67e360ceee130b967af4d50996e5154a45df46" dependencies = [ "chrono", "nom", @@ -962,12 +936,6 @@ dependencies = [ "vcpkg", ] -[[package]] -name = "linux-raw-sys" -version = "0.9.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd945864f07fe9f5371a27ad7b52a172b4b499999f1d97574c9fa68373937e12" - [[package]] name = "litemap" version = "0.8.0" @@ -1034,7 +1002,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2886843bf800fba2e3377cff24abf6379b4c4d5c6681eaf9ea5b0d15090450bd" dependencies = [ "libc", - "wasi 0.11.0+wasi-snapshot-preview1", + "wasi", "windows-sys 0.52.0", ] @@ -1255,12 +1223,6 @@ dependencies = [ "proc-macro2", ] -[[package]] -name = "r-efi" -version = "5.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74765f6d916ee2faa39bc8e68e4f3ed8949b48cccdac59983d287a7cb71ce9c5" - [[package]] name = "rand" version = "0.8.5" @@ -1288,7 +1250,7 @@ version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" dependencies = [ - "getrandom 0.2.16", + "getrandom", ] [[package]] @@ -1326,19 +1288,6 @@ version = "0.1.24" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" -[[package]] -name = "rustix" -version = "1.0.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c71e83d6afe7ff64890ec6b71d6a69bb8a610ab78ce364b3352876bb4c801266" -dependencies = [ - "bitflags", - "errno", - "libc", - "linux-raw-sys", - "windows-sys 0.59.0", -] - [[package]] name = "rustversion" version = "1.0.20" @@ -1516,9 +1465,9 @@ dependencies = [ [[package]] name = "sqlx" -version = "0.8.5" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3c3a85280daca669cfd3bcb68a337882a8bc57ec882f72c5d13a430613a738e" +checksum = "1fefb893899429669dcdd979aff487bd78f4064e5e7907e4269081e0ef7d97dc" dependencies = [ "sqlx-core", "sqlx-macros", @@ -1529,9 +1478,9 @@ dependencies = [ [[package]] name = "sqlx-core" -version = "0.8.5" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f743f2a3cea30a58cd479013f75550e879009e3a02f616f18ca699335aa248c3" +checksum = "ee6798b1838b6a0f69c007c133b8df5866302197e404e8b6ee8ed3e3a5e68dc6" dependencies = [ "base64", "bytes", @@ -1563,9 +1512,9 @@ dependencies = [ [[package]] name = "sqlx-macros" -version = "0.8.5" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f4200e0fde19834956d4252347c12a083bdcb237d7a1a1446bffd8768417dce" +checksum = "a2d452988ccaacfbf5e0bdbc348fb91d7c8af5bee192173ac3636b5fb6e6715d" dependencies = [ "proc-macro2", "quote", @@ -1576,9 +1525,9 @@ dependencies = [ [[package]] name = "sqlx-macros-core" -version = "0.8.5" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "882ceaa29cade31beca7129b6beeb05737f44f82dbe2a9806ecea5a7093d00b7" +checksum = "19a9c1841124ac5a61741f96e1d9e2ec77424bf323962dd894bdb93f37d5219b" dependencies = [ "dotenvy", "either", @@ -1595,16 +1544,15 @@ dependencies = [ "sqlx-postgres", "sqlx-sqlite", "syn", - "tempfile", "tokio", "url", ] [[package]] name = "sqlx-mysql" -version = "0.8.5" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0afdd3aa7a629683c2d750c2df343025545087081ab5942593a5288855b1b7a7" +checksum = "aa003f0038df784eb8fecbbac13affe3da23b45194bd57dba231c8f48199c526" dependencies = [ "atoi", "base64", @@ -1644,9 +1592,9 @@ dependencies = [ [[package]] name = "sqlx-postgres" -version = "0.8.5" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a0bedbe1bbb5e2615ef347a5e9d8cd7680fb63e77d9dafc0f29be15e53f1ebe6" +checksum = "db58fcd5a53cf07c184b154801ff91347e4c30d17a3562a635ff028ad5deda46" dependencies = [ "atoi", "base64", @@ -1681,9 +1629,9 @@ dependencies = [ [[package]] name = "sqlx-sqlite" -version = "0.8.5" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c26083e9a520e8eb87a06b12347679b142dc2ea29e6e409f805644a7a979a5bc" +checksum = "c2d12fe70b2c1b4401038055f90f151b78208de1f9f89a7dbfd41587a10c3eea" dependencies = [ "atoi", "flume", @@ -1754,19 +1702,6 @@ dependencies = [ "syn", ] -[[package]] -name = "tempfile" -version = "3.20.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8a64e3985349f2441a1a9ef0b853f869006c3855f2cda6862a94d26ebb9d6a1" -dependencies = [ - "fastrand", - "getrandom 0.3.3", - "once_cell", - "rustix", - "windows-sys 0.59.0", -] - [[package]] name = "thiserror" version = "2.0.12" @@ -2070,15 +2005,6 @@ version = "0.11.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" -[[package]] -name = "wasi" -version = "0.14.2+wasi-0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9683f9a5a998d873c0d21fcbe3c083009670149a8fab228644b8bd36b2c48cb3" -dependencies = [ - "wit-bindgen-rt", -] - [[package]] name = "wasite" version = "0.1.0" @@ -2177,9 +2103,9 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] name = "windows-core" -version = "0.61.0" +version = "0.61.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4763c1de310c86d75a878046489e2e5ba02c649d185f21c67d4cf8a56d098980" +checksum = "c0fdd3ddb90610c7638aa2b3a3ab2904fb9e5cdbecc643ddb3647212781c4ae3" dependencies = [ "windows-implement", "windows-interface", @@ -2218,18 +2144,18 @@ checksum = "76840935b766e1b0a05c0066835fb9ec80071d4c09a16f6bd5f7e655e3c14c38" [[package]] name = "windows-result" -version = "0.3.2" +version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c64fd11a4fd95df68efcfee5f44a294fe71b8bc6a91993e2791938abcc712252" +checksum = "56f42bd332cc6c8eac5af113fc0c1fd6a8fd2aa08a0119358686e5160d0586c6" dependencies = [ "windows-link", ] [[package]] name = "windows-strings" -version = "0.4.0" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a2ba9642430ee452d5a7aa78d72907ebe8cfda358e8cb7918a2050581322f97" +checksum = "56e6c93f3a0c3b36176cb1327a4958a0353d5d166c2a35cb268ace15e91d3b57" dependencies = [ "windows-link", ] @@ -2382,15 +2308,6 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" -[[package]] -name = "wit-bindgen-rt" -version = "0.39.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1" -dependencies = [ - "bitflags", -] - [[package]] name = "writeable" version = "0.6.1" diff --git a/Cargo.toml b/Cargo.toml index 9912617..322dfa5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -24,3 +24,4 @@ hex = "0.4.3" sha2 = "0.10.8" chrono = "0.4.33" iso8601 = { version = "0.6.2", features = ["chrono", "serde"] } +rand = "0.8.5" \ No newline at end of file diff --git a/build.rs b/build.rs index 327a3b4..1ed525c 100644 --- a/build.rs +++ b/build.rs @@ -1,4 +1,5 @@ fn main() { // trigger recompilation when a new migration is added println!("cargo::rerun-if-changed=migrations"); + } diff --git a/src/app/algorithm.rs b/src/app/algorithm.rs index b03461b..571b639 100644 --- a/src/app/algorithm.rs +++ b/src/app/algorithm.rs @@ -1,137 +1,467 @@ use std::cmp::min; use std::collections::VecDeque; -#[derive(Debug, Clone)] -struct Edge { - to: usize, - capacity: i32, - flow: i32, - rev: usize, +#[derive(Clone, Debug)] +pub struct Edge { + pub u: i32, + pub next: usize, + pub v: i32, + pub w: i32, + pub c: i32, } -#[derive(Debug, Clone)] -struct Dinic { - graph: Vec>, - n: usize, +pub struct Mcmf { + pub n: i32, + pub m: i32, + pub s: i32, + pub t: i32, + pub maxflow: i32, + pub cost: i32, + pub in_field: i32, + pub ss: i32, + pub tt: i32, + pub a0: i32, + pub a1: i32, + pub d: Vec, + pub incf: Vec, + pub pre: Vec, + pub head: Vec, + pub a: Vec, + pub vis: Vec, + pub e: Vec, + pub q: VecDeque, } -impl Dinic { - fn new(n: usize) -> Self { - Dinic { - graph: vec![Vec::new(); n], - n, - } - } +impl Mcmf { + pub const INF: i32 = 0x3f3f3f3f; - fn add_edge(&mut self, from: usize, to: usize, capacity: i32) { - let to_len = self.graph[to].len(); - let from_len = self.graph[from].len(); - self.graph[from].push(Edge { - to, - capacity, - flow: 0, - rev: to_len, + pub fn new() -> Self { + let mut m = Mcmf { + n: 0, + m: 0, + s: 0, + t: 0, + maxflow: 0, + cost: 0, + in_field: 0, + ss: 0, + tt: 0, + a0: 0, + a1: 0, + d: Vec::new(), + incf: Vec::new(), + pre: Vec::new(), + head: Vec::new(), + a: Vec::new(), + vis: Vec::new(), + e: Vec::new(), + q: VecDeque::new(), + }; + m.e.push(Edge { + u: 0, + next: 0, + v: 0, + w: 0, + c: 0, + }); + m.e.push(Edge { + u: 0, + next: 0, + v: 0, + w: 0, + c: 0, }); - self.graph[to].push(Edge { - to: from, - capacity: 0, - flow: 0, - rev: from_len, + m + } + + pub fn init(&mut self) { + self.n = 0; + self.m = 0; + self.s = 0; + self.t = 0; + self.maxflow = 0; + self.cost = 0; + self.in_field = 0; + self.ss = 0; + self.tt = 0; + self.a0 = 0; + self.a1 = 0; + self.d.clear(); + self.incf.clear(); + self.pre.clear(); + self.head.clear(); + self.a.clear(); + self.vis.clear(); + self.e.truncate(2); + self.q.clear(); + } + + pub fn set_n(&mut self, n: i32) { + let sz = (n + 3) as usize; + self.d.resize(sz, 0); + self.incf.resize(sz, 0); + self.pre.resize(sz, 0); + self.head.resize(sz, 0); + self.a.resize(sz, 0); + self.vis.resize(sz, false); + } + + fn add(&mut self, u: i32, v: i32, w: i32, c: i32) { + let idx = self.e.len(); + self.e.push(Edge { + u, + next: self.head[u as usize], + v, + w, + c, }); + self.head[u as usize] = idx; } - fn bfs(&self, s: usize, t: usize, level: &mut [i32]) -> bool { - level.fill(-1); - level[s] = 0; - let mut queue = VecDeque::new(); - queue.push_back(s); + fn add_e(&mut self, u: i32, v: i32, w: i32, c: i32) { + self.add(u, v, w, c); + self.add(v, u, 0, -c); + } - while let Some(u) = queue.pop_front() { - for edge in &self.graph[u] { - if edge.capacity - edge.flow > 0 && level[edge.to] == -1 { - level[edge.to] = level[u] + 1; - queue.push_back(edge.to); + pub fn add_edge(&mut self, u: i32, v: i32, l: i32, d: i32, c: i32) { + self.a[v as usize] += l; + self.a[u as usize] -= l; + self.add_e(u, v, d - l, c); + } + + pub fn add_signed(&mut self, u: i32, v: i32, w: i32, c: i32) { + if c >= 0 { + self.add_edge(u, v, 0, w, c); + } else { + self.a[v as usize] += w; + self.a[u as usize] -= w; + self.add_edge(v, u, 0, w, -c); + self.a1 += c * w; + } + } + + fn spfa(&mut self) -> bool { + self.q.clear(); + self.vis.fill(false); + self.d.fill(Self::INF); + self.q.push_back(self.ss); + self.d[self.ss as usize] = 0; + self.incf[self.ss as usize] = std::i32::MAX; + while let Some(u) = self.q.pop_front() { + self.vis[u as usize] = false; + let mut i = self.head[u as usize]; + while i != 0 { + let e = &self.e[i]; + if e.w > 0 && self.d[u as usize] + e.c < self.d[e.v as usize] { + self.d[e.v as usize] = self.d[u as usize] + e.c; + self.pre[e.v as usize] = i; + self.incf[e.v as usize] = min(self.incf[u as usize], e.w); + if !self.vis[e.v as usize] { + self.vis[e.v as usize] = true; + self.q.push_back(e.v); + } } + i = e.next; } } - level[t] != -1 + self.d[self.tt as usize] != Self::INF } - fn dfs( - &mut self, - u: usize, - t: usize, - level: &Vec, - flow: i32, - start: &mut Vec, - ) -> i32 { - if u == t { - return flow; - } - while start[u] < self.graph[u].len() { - let i = start[u]; - let (capacity, to, rev) = { - let edge = &self.graph[u][i]; - (edge.capacity - edge.flow, edge.to, edge.rev) - }; + /// 沿增广路更新流与费用 + fn update(&mut self) { + let mut x = self.tt; + let flow = self.incf[self.tt as usize]; + while x != self.ss { + let i = self.pre[x as usize]; + self.e[i].w -= flow; + let ri = i ^ 1; + self.e[ri].w += flow; + x = self.e[ri].v; + } + self.maxflow += flow; + self.cost += self.d[self.tt as usize] * flow; + } - if capacity > 0 && level[to] == level[u] + 1 { - let pushed = self.dfs(to, t, level, min(flow, capacity), start); - if pushed > 0 { - self.graph[u][i].flow += pushed; - self.graph[to][rev].flow -= pushed; - return pushed; - } - } - start[u] += 1; + /// 主循环:不断 SPFA + 更新 + pub fn work(&mut self) { + while self.spfa() { + self.update(); } - 0 } - fn max_flow(&mut self, s: usize, t: usize) -> i32 { - let mut total_flow = 0; - let mut level = vec![0; self.n]; - while self.bfs(s, t, &mut level) { - let mut start = vec![0; self.n]; - while let Some(flow) = Some(self.dfs(s, t, &level, i32::MAX, &mut start)) { - if flow == 0 { - break; - } - total_flow += flow; + /// 求解 + pub fn solve(&mut self) { + // 构造超级源汇 + self.ss = self.n + 1; + self.tt = self.n + 2; + for i in 1..=self.n { + let ai = self.a[i as usize]; + if ai > 0 { + self.add_edge(self.ss, i, 0, ai, 0); + } else if ai < 0 { + self.add_edge(i, self.tt, 0, -ai, 0); } } - total_flow + // 保证原图中循环流可行 + self.add_edge(self.tt, self.ss, 0, Self::INF, 0); + self.work(); + + // 切换为真正的源汇 + self.ss = self.s; + self.tt = self.t; + self.a1 += self.cost; + self.maxflow = 0; + self.cost = 0; + // 将最后两条边容量置零 + let len = self.e.len(); + self.e[len - 1].w = 0; + self.e[len - 2].w = 0; + self.work(); + self.a0 += self.maxflow; + self.a1 += self.cost; } } -pub fn max_flow(users: Vec<(i64, Vec)>, spares: Vec) -> Vec> { - let mut dinic = Dinic::new(users.len() * 7 + spares.len() + 2); - let s = users.len() * 7 + spares.len(); - let t = users.len() * 7 + spares.len() + 1; - let mut user_nodes = Vec::new(); - for (user_id, stamps) in users { - let n = spares.len() + user_nodes.len(); - for day in 0..7 { - user_nodes.push(user_id); - dinic.add_edge(n + day, t, 1); +use std::error::Error; + +#[derive(Clone, Debug)] +pub struct User { + pub id: u64, + pub stamps: Vec, +} + +#[derive(Clone, Debug)] +pub struct Spare { + pub stamp: u64, + pub day: u64, +} + +pub struct Distribution { + user: Vec, + spare: Vec, + mf: Mcmf, +} + +impl Distribution { + pub fn new() -> Self { + Distribution { + user: Vec::new(), + spare: Vec::new(), + mf: Mcmf::new(), + } + } + + pub fn init( + &mut self, + users: &[User], + spares: &[Spare], + spare_size: usize, + ) -> Result<(), Box> { + if spare_size != spares.len() { + return Err("spare_size != spares.len()".into()); + } + self.user.clear(); + self.spare.clear(); + self.user.reserve(users.len()); + self.spare.reserve(spare_size); + self.user.extend_from_slice(users); + self.spare.extend_from_slice(spares); + Ok(()) + } + + pub fn solve(&mut self) -> Vec { + self.mf.init(); + let s = 1; + let n_nodes = (self.user.len() * 8) as i32 + self.spare.len() as i32 + 2; + let t = n_nodes; + self.mf.n = n_nodes; + self.mf.s = s; + self.mf.t = t; + self.mf.set_n(n_nodes); + + for i in 0..self.user.len() { + let u = (i + 2) as i32; + self.mf.add_edge(s, u, 0, 1, 20); + self.mf.add_edge(s, u, 0, 1, 50); + self.mf.add_edge(s, u, 0, 1, 100); + } + for day in 1..=7 { + for j in 0..self.user.len() { + let from = (j + 2) as i32; + let to = (j + 2 + day * self.user.len()) as i32; + self.mf.add_edge(from, to, 0, 1, 0); + } + } + for (i, user) in self.user.iter().enumerate() { + for &stamp in &user.stamps { + let from = + (i as u64 + 2 + (self.spare[stamp as usize].day + 1) * self.user.len() as u64) + as i32; + let to = + n_nodes - self.spare.len() as i32 + self.spare[stamp as usize].stamp as i32; + self.mf.add_edge(from, to, 0, 1, 0); + } } - for stamp in stamps { - let user_node = n + spares[stamp]; - dinic.add_edge(stamp, user_node, 1); + for i in 0..self.spare.len() { + let from = n_nodes - self.spare.len() as i32 + i as i32; + self.mf.add_edge(from, t, 0, 1, 0); } + self.mf.solve(); + let a0 = 1 + self.user.len() as i64; + let a1 = 1 + (self.user.len() * 8) as i64; + let a2 = (self.mf.n - 1) as i64; + let mut res = vec![ + User { + id: 0, + stamps: Vec::new() + }; + self.user.len() + ]; + for e in &self.mf.e { + let u = e.u as i64; + let v = e.v as i64; + if u > a0 && u <= a1 && v > a1 && v <= a2 && e.w == 0 { + let idx = ((u - a0 - 1) % self.user.len() as i64) as usize; + let stamp = (v - a1 - 1) as u64; + res[idx].stamps.push(stamp); + } + } + for (i, user) in self.user.iter().enumerate() { + res[i].id = user.id; + res[i].stamps.sort_unstable(); + } + res } - for stamp in 0..spares.len() { - dinic.add_edge(s, stamp, 1); +} + +pub fn distribute(users: Vec, spares: Vec) -> Vec { + let mut sol = Distribution::new(); + sol.init(&users, &spares, spares.len()) + .expect("spare_size must equal spares.len()"); + sol.solve() +} + +#[cfg(test)] +mod tests { + use super::*; + + use rand::{thread_rng, seq::SliceRandom}; + #[test] + fn test_mcmf_sample1() { + let mut mf = Mcmf::new(); + mf.n = 4; + mf.s = 4; + mf.t = 3; + mf.set_n(4); + + mf.add_signed(4, 2, 30, 2); + mf.add_signed(4, 3, 20, 3); + mf.add_signed(2, 3, 20, 1); + mf.add_signed(2, 1, 30, 9); + mf.add_signed(1, 3, 40, 5); + + mf.solve(); + + assert_eq!(mf.a0, 50, "期望最大流 a0 = 50,但实际是 {}", mf.a0); + assert_eq!(mf.a1, 280, "期望最小费用 a1 = 280,但实际是 {}", mf.a1); } - dinic.max_flow(s, t); - (0..spares.len()) - .map(|stamp| { - for edge in dinic.graph[stamp].iter() { - if edge.flow > 0 { - return Some(user_nodes[edge.to - spares.len()]); - } + #[test] + fn test_mcmf_sample2() { + let mut mf = Mcmf::new(); + mf.n = 5; + mf.s = 1; + mf.t = 5; + mf.set_n(5); + + mf.add_signed(1, 3, 2, 4); + mf.add_signed(1, 2, 2, 3); + mf.add_signed(3, 5, 2, 2); + mf.add_signed(3, 2, 1, -1); + mf.add_signed(2, 4, 2, -2); + mf.add_signed(4, 3, 1, -1); + mf.add_signed(4, 5, 1, 3); + + mf.solve(); + + assert_eq!(mf.a0, 3, "期望最大流 a0 = 3,但实际是 {}", mf.a0); + assert_eq!(mf.a1, 12, "期望最小费用 a1 = 12,但实际是 {}", mf.a1); + } + // empty_test + #[test] + fn test_distribution_sample1() { + let users = vec![User { + id: 0, + stamps: vec![], + }]; + let spares = vec![Spare { day: 0, stamp: 0 }]; + let res = distribute(users, spares); + println!("res: {:?}", res); + assert_eq!(res.len(), 1, "期望结果长度为 1,但实际是 {}", res.len()); + } + #[test] + fn test_distribution_sample2() { + let users = vec![User { + id: 0, + stamps: vec![0], + }]; + let spares = vec![Spare { day: 0, stamp: 0 }]; + let res = distribute(users, spares); + // println!("res: {:?}", res); + assert_eq!(res.len(), 1, "期望结果长度为 1,但实际是 {}", res.len()); + } + + #[test] + fn test_distribution_sample3() { + let users = vec![User { + id: 0, + stamps: vec![0, 1], + }]; + let spares = vec![Spare { day: 0, stamp: 0 }, Spare { day: 0, stamp: 1 }]; + let res = distribute(users, spares); + // println!("res: {:?}", res); + assert_eq!(res.len(), 1, "期望结果长度为 1,但实际是 {}", res.len()); + } + + #[test] + fn test_distribution_sample4() { + let mut spares = Vec::new(); + for day in 0..7 { + for slot in 0..2 { + spares.push(Spare { + stamp: (day * 2 + slot) as u64, + day: day as u64, + }); } - None - }) - .collect() + } + + let mut rng = thread_rng(); + let all_slots: Vec = (0..14).collect(); + let users: Vec = (0..10) + .map(|i| { + let mut picks = all_slots.clone(); + picks.shuffle(&mut rng); + picks.truncate(5); + User { id: i as u64, stamps: picks } + }) + .collect(); + + println!("初始"); + for user in &users { + println!(" 用户 {}: 时隙 {:?}", user.id, user.stamps); + } + let result = distribute(users.clone(), spares.clone()); + + println!("分配结果:"); + for user in &result { + println!(" 用户 {}: 时隙 {:?}", user.id, user.stamps); + } + + assert_eq!(result.len(), users.len(), "结果用户数量应与输入一致"); + for user in result { + assert!(user.stamps.len() <= 5, "用户分配时隙不能超过 5"); + for &stamp in &user.stamps { + assert!(stamp < 14, "时隙索引应在 0..14 范围内"); + } + } + } }