1212#include < unordered_set>
1313#include < vector>
1414
15-
16-
1715/* *
1816 * @brief creates a vector of unique elements with exactly one element from each given range. order is
1917 * preserved
@@ -97,36 +95,37 @@ template<mta::range2d_over_cpt<CPT(std::equality_comparable)> Rng>
9795
9896}
9997
100-
101-
10298namespace cth ::alg {
10399
104100/* *
105- * \brief assigns a's to b's based of every a's options for b's
101+ * \brief assigns a's to b's based of every a's options for b's, prioritizing spread
106102 * \tparam Rng1 rng<rng<std::integral>>
107103 * \tparam Rng2 rng<std::integral>
108104 * \param a_b_options range of b options from a's
109105 * \param b_max max assignments for b's
110106 * \return vector<integral> based on Rng1
111107 */
112108template <mta::range2d_over_cpt<CPT(std::integral)> Rng1, mta::range_over_cpt<CPT(std::integral)> Rng2>
113- [[nodiscard]] auto assign (Rng1 const & a_b_options, Rng2 const & b_max) -> std::vector<mta::range2d_value_t<Rng1>> {
109+ [[nodiscard]] auto assign (
110+ Rng1 const & a_b_options,
111+ Rng2 const & b_max
112+ ) -> std::vector<mta::range2d_value_t<Rng1>> {
114113 using T = std::remove_cvref_t <mta::range2d_value_t <Rng1>>;
115114 static constexpr size_t UNASSIGNED = static_cast <size_t >(-1 );
116115
117116 CTH_CRITICAL (
118117 !std::ranges::all_of (
119118 a_b_options,
120119 [&b_max](std::span<T const > b_options) {
121- return std::ranges::all_of (b_options, [&b_max](auto const index) {
120+ return std::ranges::all_of (b_options,
121+ [&b_max](auto const index) {
122122 return 0 <= index && index < std::ranges::size (b_max);
123- });
123+ }
124+ );
124125 }
125126 ),
126127 " 0 <= indices < size(b_max) required"
127- ) {
128- details->add (" size(b_max): {}" , std::ranges::size (b_max));
129- }
128+ ) { details->add (" size(b_max): {}" , std::ranges::size (b_max)); }
130129
131130 auto const aCount = std::ranges::size (a_b_options);
132131 auto const bCount = std::ranges::size (b_max);
@@ -146,6 +145,10 @@ template<mta::range2d_over_cpt<CPT(std::integral)> Rng1, mta::range_over_cpt<CPT
146145 std::vector<bool > visitedB (bCount, false );
147146 std::vector<bool > visitedA (aCount, false );
148147
148+ // NEW: State for "Water-filling" spreading
149+ size_t assignedCount = 0 ;
150+ std::vector<size_t > current_b_max (bCount, 0 );
151+
149152 // 2. Isolated BFS Engine
150153 auto find_augmenting_path = [&](size_t startA) -> size_t {
151154 std::ranges::fill (visitedB, false );
@@ -165,55 +168,73 @@ template<mta::range2d_over_cpt<CPT(std::integral)> Rng1, mta::range_over_cpt<CPT
165168 visitedB[b] = true ;
166169 parentB[b] = currentA; // Record path
167170
168- // Path End: Open slot found
169- if (std::ranges::size (bToA[b]) < static_cast < size_t >(b_max [b]) )
171+ // MODIFIED: Compare against the current water level, NOT absolute max
172+ if (std::ranges::size (bToA[b]) < current_b_max [b])
170173 return b;
171174
172175 // Path Continuation: Displace existing residents
173- for (auto previousA : bToA[b]) {
176+ for (auto previousA : bToA[b])
174177 if (!visitedA[previousA]) {
175178 visitedA[previousA] = true ;
176179 parentA[previousA] = b;
177180 q.push (previousA);
178181 }
179- }
180182 }
181183 }
182184 return UNASSIGNED;
183185 };
184186
185- // 3. Main Logic
186- for (size_t i = 0 ; i < aCount; ++i) {
187- auto endB = find_augmenting_path (i);
187+ // 3. Main Logic (Water-filling Loop)
188+ bool capacity_increased = true ;
188189
189- if (endB == UNASSIGNED) return std::vector<T>{}; // Dead end
190+ // Loop until all 'a's are assigned or we've exhausted true capacities
191+ while (assignedCount < aCount && capacity_increased) {
192+ capacity_increased = false ;
190193
191- // Backtrack and flip assignments
192- auto currentB = endB;
193- while (true ) {
194- auto currentA = parentB[currentB];
194+ // Step 3a: Raise the water level (allow 1 more allocation per family)
195+ for (size_t i = 0 ; i < bCount; ++i)
196+ if (current_b_max[i] < static_cast <size_t >(b_max[i])) {
197+ current_b_max[i]++;
198+ capacity_increased = true ;
199+ }
200+
201+ // Step 3b: Try to assign all unassigned elements at this new capacity level
202+ for (size_t i = 0 ; i < aCount; ++i) {
203+ if (aToB[i] != static_cast <T>(UNASSIGNED)) continue ; // Already assigned
204+
205+ auto endB = find_augmenting_path (i);
206+
207+ if (endB != UNASSIGNED) {
208+ // Backtrack and flip assignments
209+ auto currentB = endB;
210+ while (true ) {
211+ auto currentA = parentB[currentB];
195212
196- if (currentA != i) {
197- auto previousB = parentA[currentA];
213+ if (currentA != i) {
214+ auto previousB = parentA[currentA];
198215
199- // O(1) Swap-and-pop
200- auto it = std::ranges::find (bToA[previousB], currentA);
201- *it = bToA[previousB].back ();
202- bToA[previousB].pop_back ();
216+ // O(1) Swap-and-pop
217+ auto it = std::ranges::find (bToA[previousB], currentA);
218+ *it = bToA[previousB].back ();
219+ bToA[previousB].pop_back ();
203220
204- bToA[currentB].push_back (currentA);
205- aToB[currentA] = static_cast <T>(currentB);
221+ bToA[currentB].push_back (currentA);
222+ aToB[currentA] = static_cast <T>(currentB);
206223
207- currentB = previousB; // Move up
208- } else {
209- bToA[currentB].push_back (currentA);
210- aToB[currentA] = static_cast <T>(currentB);
211- break ;
224+ currentB = previousB; // Move up
225+ } else {
226+ bToA[currentB].push_back (currentA);
227+ aToB[currentA] = static_cast <T>(currentB);
228+ break ;
229+ }
230+ }
231+ assignedCount++; // Successfully placed an 'a'
212232 }
213233 }
214234 }
215235
236+ if (assignedCount < aCount) return std::vector<T>{}; // Dead end
237+
216238 return aToB;
217239}
218-
219240}
0 commit comments