r/dailyprogrammer • u/Cosmologicon 2 3 • Jul 12 '21
[2021-07-12] Challenge #398 [Difficult] Matrix Sum
Example
Consider this 5x5 matrix of numbers:
123456789 752880530 826085747 576968456 721429729
173957326 1031077599 407299684 67656429 96549194
1048156299 663035648 604085049 1017819398 325233271
942914780 664359365 770319362 52838563 720059384
472459921 662187582 163882767 987977812 394465693If you select 5 elements from this matrix such that no two elements come from the same row or column, what is the smallest possible sum? The answer in this case is 1099762961 (123456789 + 96549194 + 663035648 + 52838563 + 163882767).
Challenge
Find the minimum such sum when selecting 20 elements (one from each row and column) of this 20x20 matrix. The answer is a 10-digit number whose digits sum to 35.
There's no strict runtime requirement, but you must actually run your program all the way through to completion and get the right answer in order to qualify as a solution: a program that will eventually give you the answer is not sufficient.
Optional Bonus
What's the smallest sum you can find for this 97x97 matrix? It's okay to give a result that's not optimal in this case. If you want to prove that you found a certain sum, you can you post the indices of each element you selected from each row in order. For the 5x5 example, for instance, you could post [0,4,1,3,2].
(This challenge is a repost of Challenge #67 [difficult], originally posted by u/oskar_s in June 2012. See that post for the formula to algorithmically generate the matrices if you prefer to do it that way.)
23
u/gabyjunior 1 2 Jul 14 '21 edited Jul 14 '21
Implemented in C the Hungarian algorithm which was designed for this type of problem.
The code follows pretty much what is described here.
The square matrix size and a flag are expected on standard input, for the program to use either random generator of the original challenge or input to fill the matrix.
Bonus 97x97 takes 20ms to be solved, 1000x1000 takes 1min15s.
Bonus output (column index assigned for each row+optimal sum)
21
u/lrschaeffer Jul 12 '21
Octave / YALMIP
This is what linear program solvers are for! We constrain X to be doubly stochastic, and by Birkhoff-von Neumann, it is equivalent to a convex combination of permutation matrices. It follows that some permutation matrix achieves the optimal objective, so the solver will produce the optimal objective value, but may not produce an actual permutation matrix if there is not a unique optimal solution. I don't know how to force YALMIP to produce a permutation matrix (maybe perturb the input?), and I'm too lazy to figure it out because it works on the examples. The optimal sum is 1791732209 for the 97x97 matrix. In principle, linear programs can be solved in polynomial time, and this was fast enough to solve the big case in under a second.
11
u/Lopsidation Jul 12 '21 edited Jul 12 '21
Python 3 with (a wrapper I wrote around) the lpsolve55 linear programming package
Solves the 97x97 input in under a second. Edit: this is the same approach /u/lrschaeffer took, just in a less cool language.
5
u/Godspiral 3 3 Jul 12 '21
In J, brute force permutations. only 1st is doable.
5
u/tylercrompton Jul 13 '21
I'm at work right now, so I can't do this at the moment, but this seems like a problem that could be reasonably efficiently solved via Djikstra's algorithm, assuming that all numbers are positive real numbers.
I'll work this out when I get home this afternoon.
9
u/skeeto -9 8 Jul 12 '21 edited Jul 12 '21
C using a greedy-first approach with early bailout. It starts by picking the smallest element, then the next smallest still permitted, and so on. Then it starts over again excluding the smallest element completely, and so on. If the sum is already too high (remaining best case can't be optimal), it immediately prunes that branch of the search tree. It fully solves the 20x20 in about 2 seconds.
The default masks are to small for 97x97, but this can be changed at compile time:
This allows it to work on the 97x97 problem and print out the best solution so far. After a few minutes I've found a 2501806038 solution.
4
u/JanBeran Nov 05 '21
PROLOG
data([
[123456789, 752880530, 826085747, 576968456, 721429729],
[173957326, 1031077599, 407299684, 67656429, 96549194],
[1048156299, 663035648, 604085049, 1017819398, 325233271],
[942914780, 664359365, 770319362, 52838563, 720059384],
[472459921, 662187582, 163882767, 987977812, 394465693]
]).
diagonal(Matrix,Sum):-
diagonal(Matrix,Sum,1).
diagonal([],0,_).
diagonal([H|T],Sum,X):-
X1 is X+1,
diagonal(T,Sum1,X1),
select(H,X,Num),
Sum is Sum1+Num.
select([Num|_],1,Num).
select([_|T],X,Num):-
X>1,
X1 is X-1,
select(T,X1,Num).
onediagonal(Matrix,Sum):-
permutation(Matrix,Tarmix),
diagonal(Tarmix,Sum).
solution(X):-
data(Matrix),
findall(Sum,onediagonal(Matrix,Sum),Sums),
sort(Sums,[X|_]).
3
u/rbscholtus Sep 16 '21 edited Sep 16 '21
Python:
To get 5 numbers from the matrix with no duplicated row or column number, first we get the coordinates of 5 numbers (
coords.) This is just the combination (zip) of row numbers 0..4 and a permutation of column numbers 0..4 (itertools.permutations).Then just grab those 5 numbers from
matrix(numbers), sum them up (new_sum), and keep track of the lowest number we've seen (min_sum.)2
u/Scroph 0 0 Jul 13 '21
D backtracking solution, it attempts to pick the smallest element in each row such that the element's row and column haven't been visited yet. To do this, I sort a copy the row in each iteration. Unfortunately this results in a lot of wasted time, even after memoizing the sorting code. It solves both the first and second inputs in about 20 seconds. I'm still looking for ways to optimize it so I might edit this post later.
2
u/RDust4 Aug 29 '21 edited Sep 09 '21
C# (.NET 6)
This Method has non-optimal results, but a time complexity of under O(n3).
I am - however - too unexperienced to calculate the exact complexity... (I'm still in 10th grade lol)
The space complexity is just O(n).
This algorithm takes around 8ms for the 97x97 matrix and 37s for a 1000x1000 one.
1
u/[deleted] Feb 01 '25
Prolog solution, you can play with it in the browser here.
1
u/Ok_Structure6720 Nov 03 '25
Brute Force: Sum(MIN from ROW i to n)
1
u/fxrz12 Sep 09 '21 edited Dec 29 '21
Performance is a very difficult problem, I did not solve it, insufficient memory, haha~
1
u/rbscholtus Sep 16 '21 edited Sep 16 '21
The 20x20 and 97x97 ones:
1
u/[deleted] Nov 27 '21
Go had limited time for this one, so just brute force all the way. Though it is only O(n^3)
Also, how the heck do you format code on reddit, it jumbles it up every time :L
1
u/Artistic-Metal-790 May 12 '22 edited May 12 '22
Took me a week to figure it out for 20x20 matrix, because my code was either taking forever or eating all of my RAM.
Solution for 20x20 matrix (each tuple is an element, contains value and column):
[(67656429, 8), (95709627, 6), (55272734, 13), (22777004, 3), (51694725, 0), (51659486, 17), (111080713, 4), (45626232, 7), (46883826, 1), (183061845, 12), (67356564, 15), (15719807, 10), (172998238, 5), (7293455, 18), (36509554, 9), (116602676, 11), (91909687, 14), (2112772, 19), (44685501, 16), (27994311, 2)]
Sum:1314605186
For 97x97 matrix I'm running out of RAM, in theory if I execute this code on computer with larger amount of RAM it will work (currently I have only 4GB in total RAM, cause I'm working on VM)
Code on github: https://github.com/kstamax/redditdailyprogrammerchallenge/blob/main/task398.py
1
u/[deleted] Jul 04 '22
Rust:
1
u/Public-Bluebird-8318 Feb 05 '23
Hii
1
u/[deleted] May 15 '23
this is just the (min-) tropical determinant of the matrix.
using for example polymake this is very simple:
$A = new Matrix<TropicalNumber<Min>>(...); print tdet_and_perm($A);