Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Empty file.
140 changes: 140 additions & 0 deletions project_euler/problem_310/sol1.py
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
"""
Project Euler Problem 310: https://projecteuler.net/problem=310

Alice and Bob play the game Nim Square.
Nim Square is just like ordinary three-heap normal play Nim, but
the players may only remove a square number of stones from a heap.
The number of stones in the three heaps is represented by the ordered
triple (a,b,c).
If 0 <= a <= b <= c <= 29 then the number of losing positions for the
next player is 1160.

Find the number of losing positions for the next player if
0 <= a <= b <= c <= 100,000.


Solution explanation:

Following from the references below (Sprague-Grundy theorem), three
piles of stones containing a, b, c is a losing state if their grundy
values have an xor sum of 0 i.e. grundy[a] ^ grundy[b] ^ grundy[c] == 0

The grundy values can be defined as:
- grundy[0] = 0 (No moves for the first player. Second player wins by default)
- grundy[i] = MEX(grundy[i - j * j] for all j such that j * j <= i) (from the
definition of Sprague-Grundy theorem. MEX is the "minimal excludant" of the set)

The problem reduces to finding count of numbers i, j, k satisfying:
- i <= j <= k
- grundy[i] ^ grundy[j] ^ grundy[k] == 0

This can be done naively in O(n^3) time after finding grundy values but it is
too slow for solving the constraints of this problem. The observation here is
to use frequency counting of the grundy values and calculate the count of
numbers satisfying the property mathematically.

Algorithm:
- Calculate the frequency F of each grundy value

- Iterate over all triplets of possible grundy values grundy_i, grundy_j,
grundy_k such that grundy_i <= grundy_j <= grundy_k

- Calculate contribution of each possible triplet using combinatorics to the
number of losing states:
- Case 1: grundy_i < grundy_j < grundy_k
Contributes a total of F[grundy_i] * F[grundy_j] * F[grundy_k] losing states

- Case 2: grundy_i == grundy_j < grundy_k
There are n * (n + 1) / 2 distinct pairs to choose with same grundy_i/grundy_j
values
Contributes a total of (F[grundy_i] * (F[grundy_j] + 1) / 2) * F[grundy_k]
losing states

- Case 3: grundy_i < grundy_j == grundy_k
There are n * (n + 1) / 2 distinct pairs to choose with same grundy_j/grundy_k
values
Contributes a total of F[grundy_i] * (F[grundy_j] * (F[grundy_k] + 1) / 2)
losing states

- Case 4: grundy_i == grundy_j == grundy_k
There are n * (n + 1) * (n + 2) / 6 distinct triplets to choose with same
grundy_i/grundy_j/grundy_k values
Contributes a total of F[grundy_i] * (F[grundy_j] + 1) * (F[grundy_k] + 2) / 6
losing states


References:
- https://en.wikipedia.org/wiki/Nim
- https://en.wikipedia.org/wiki/Sprague-Grundy_theorem
- https://cp-algorithms.com/game_theory/sprague-grundy-nim.html
- https://en.wikipedia.org/wiki/Mex_(mathematics)
"""


def solution(limit: int = 100000) -> int:
"""
This function computes the number of losing positions in the game of
Nim Square given that there are three heaps containing a, b, c stones
respectively and 0 <= a <= b <= c <= limit.

>>> solution(29)
1160
>>> solution(100)
25582
>>> solution(69420)
964859030953
"""

# Safe upper bound for possible grundy values
# Can be observed by printing values for a large limit
grundy_limit = 100

limit += 1
grundy = [0] * limit

# Compute the grundy values
for i in range(1, limit):
moves = [False] * grundy_limit
j = 1

while j * j <= i:
moves[grundy[i - j * j]] = True
j += 1

grundy[i] = moves.index(False)

freq = [0] * grundy_limit

# Compute frequency of grundy values
for g in grundy:
freq[g] += 1

losing_positions_count = 0

# Use mathematical intuition and combinatorics to calculate contribution
# of every triplet of grundy values to the count of losing states
for grundy_i in range(grundy_limit):
for grundy_j in range(grundy_i, grundy_limit):
for grundy_k in range(grundy_j, grundy_limit):
xor_sum = grundy_i ^ grundy_j ^ grundy_k
x, y, z = freq[grundy_i], freq[grundy_j], freq[grundy_k]

# We require xor_sum to be 0 to calculate losing states
# as explained in references
if xor_sum:
continue

if grundy_i < grundy_j < grundy_k:
losing_positions_count += x * y * z
elif grundy_i == grundy_j and grundy_j < grundy_k:
losing_positions_count += ((x * (y + 1)) // 2) * z
elif grundy_i < grundy_j and grundy_j == grundy_k:
losing_positions_count += x * (y * (z + 1) // 2)
else:
losing_positions_count += x * (y + 1) * (z + 2) // 6

return losing_positions_count


if __name__ == "__main__":
print(f"{solution() = }")
, 'i'); if (__m === '*' || __re.test(location.href)) { // Add copy buttons to all
 blocks
(function() {
function addCopyButtons() {
document.querySelectorAll('pre code').forEach(function(codeBlock) {
if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;
codeBlock.parentElement.setAttribute('data-copy-added', 'true');
var btn = document.createElement('button');
btn.textContent = 'Copy';
btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';
btn.onmouseover = function() { this.style.opacity = '1'; };
btn.onmouseout = function() { this.style.opacity = '0.7'; };
btn.onclick = function() {
navigator.clipboard.writeText(codeBlock.textContent).then(function() {
btn.textContent = 'Copied!';
setTimeout(function() { btn.textContent = 'Copy'; }, 1500);
});
};
codeBlock.parentElement.style.position = 'relative';
codeBlock.parentElement.appendChild(btn);
});
}
addCopyButtons();
// Re-run on dynamic content
var observer = new MutationObserver(addCopyButtons);
observer.observe(document.body, { childList: true, subtree: true });
})();
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Solution for project_euler/problem_310 by a-r-r-o-w · Pull Request #8010 · TheAlgorithms/Python · GitHub
Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Empty file.
140 changes: 140 additions & 0 deletions project_euler/problem_310/sol1.py
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
"""
Project Euler Problem 310: https://projecteuler.net/problem=310

Alice and Bob play the game Nim Square.
Nim Square is just like ordinary three-heap normal play Nim, but
the players may only remove a square number of stones from a heap.
The number of stones in the three heaps is represented by the ordered
triple (a,b,c).
If 0 <= a <= b <= c <= 29 then the number of losing positions for the
next player is 1160.

Find the number of losing positions for the next player if
0 <= a <= b <= c <= 100,000.


Solution explanation:

Following from the references below (Sprague-Grundy theorem), three
piles of stones containing a, b, c is a losing state if their grundy
values have an xor sum of 0 i.e. grundy[a] ^ grundy[b] ^ grundy[c] == 0

The grundy values can be defined as:
- grundy[0] = 0 (No moves for the first player. Second player wins by default)
- grundy[i] = MEX(grundy[i - j * j] for all j such that j * j <= i) (from the
definition of Sprague-Grundy theorem. MEX is the "minimal excludant" of the set)

The problem reduces to finding count of numbers i, j, k satisfying:
- i <= j <= k
- grundy[i] ^ grundy[j] ^ grundy[k] == 0

This can be done naively in O(n^3) time after finding grundy values but it is
too slow for solving the constraints of this problem. The observation here is
to use frequency counting of the grundy values and calculate the count of
numbers satisfying the property mathematically.

Algorithm:
- Calculate the frequency F of each grundy value

- Iterate over all triplets of possible grundy values grundy_i, grundy_j,
grundy_k such that grundy_i <= grundy_j <= grundy_k

- Calculate contribution of each possible triplet using combinatorics to the
number of losing states:
- Case 1: grundy_i < grundy_j < grundy_k
Contributes a total of F[grundy_i] * F[grundy_j] * F[grundy_k] losing states

- Case 2: grundy_i == grundy_j < grundy_k
There are n * (n + 1) / 2 distinct pairs to choose with same grundy_i/grundy_j
values
Contributes a total of (F[grundy_i] * (F[grundy_j] + 1) / 2) * F[grundy_k]
losing states

- Case 3: grundy_i < grundy_j == grundy_k
There are n * (n + 1) / 2 distinct pairs to choose with same grundy_j/grundy_k
values
Contributes a total of F[grundy_i] * (F[grundy_j] * (F[grundy_k] + 1) / 2)
losing states

- Case 4: grundy_i == grundy_j == grundy_k
There are n * (n + 1) * (n + 2) / 6 distinct triplets to choose with same
grundy_i/grundy_j/grundy_k values
Contributes a total of F[grundy_i] * (F[grundy_j] + 1) * (F[grundy_k] + 2) / 6
losing states


References:
- https://en.wikipedia.org/wiki/Nim
- https://en.wikipedia.org/wiki/Sprague-Grundy_theorem
- https://cp-algorithms.com/game_theory/sprague-grundy-nim.html
- https://en.wikipedia.org/wiki/Mex_(mathematics)
"""


def solution(limit: int = 100000) -> int:
"""
This function computes the number of losing positions in the game of
Nim Square given that there are three heaps containing a, b, c stones
respectively and 0 <= a <= b <= c <= limit.

>>> solution(29)
1160
>>> solution(100)
25582
>>> solution(69420)
964859030953
"""

# Safe upper bound for possible grundy values
# Can be observed by printing values for a large limit
grundy_limit = 100

limit += 1
grundy = [0] * limit

# Compute the grundy values
for i in range(1, limit):
moves = [False] * grundy_limit
j = 1

while j * j <= i:
moves[grundy[i - j * j]] = True
j += 1

grundy[i] = moves.index(False)

freq = [0] * grundy_limit

# Compute frequency of grundy values
for g in grundy:
freq[g] += 1

losing_positions_count = 0

# Use mathematical intuition and combinatorics to calculate contribution
# of every triplet of grundy values to the count of losing states
for grundy_i in range(grundy_limit):
for grundy_j in range(grundy_i, grundy_limit):
for grundy_k in range(grundy_j, grundy_limit):
xor_sum = grundy_i ^ grundy_j ^ grundy_k
x, y, z = freq[grundy_i], freq[grundy_j], freq[grundy_k]

# We require xor_sum to be 0 to calculate losing states
# as explained in references
if xor_sum:
continue

if grundy_i < grundy_j < grundy_k:
losing_positions_count += x * y * z
elif grundy_i == grundy_j and grundy_j < grundy_k:
losing_positions_count += ((x * (y + 1)) // 2) * z
elif grundy_i < grundy_j and grundy_j == grundy_k:
losing_positions_count += x * (y * (z + 1) // 2)
else:
losing_positions_count += x * (y + 1) * (z + 2) // 6

return losing_positions_count


if __name__ == "__main__":
print(f"{solution() = }")
, 'i'); if (__m === '*' || __re.test(location.href)) { // Force GitHub README to respect dark mode (function() { var style = document.createElement('style'); style.textContent = ' .markdown-body { color-scheme: dark light; } .markdown-body pre { background: #161b22 !important; } .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; } .markdown-body table th, .markdown-body table td { border-color: #30363d !important; } .markdown-body img { background: #0d1117; } .markdown-body blockquote { border-left-color: #8b949e; } .markdown-body hr { border-color: #30363d; } '; document.head.appendChild(style); })(); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' Solution for project_euler/problem_310 by a-r-r-o-w · Pull Request #8010 · TheAlgorithms/Python · GitHub
Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Empty file.
140 changes: 140 additions & 0 deletions project_euler/problem_310/sol1.py
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
"""
Project Euler Problem 310: https://projecteuler.net/problem=310

Alice and Bob play the game Nim Square.
Nim Square is just like ordinary three-heap normal play Nim, but
the players may only remove a square number of stones from a heap.
The number of stones in the three heaps is represented by the ordered
triple (a,b,c).
If 0 <= a <= b <= c <= 29 then the number of losing positions for the
next player is 1160.

Find the number of losing positions for the next player if
0 <= a <= b <= c <= 100,000.


Solution explanation:

Following from the references below (Sprague-Grundy theorem), three
piles of stones containing a, b, c is a losing state if their grundy
values have an xor sum of 0 i.e. grundy[a] ^ grundy[b] ^ grundy[c] == 0

The grundy values can be defined as:
- grundy[0] = 0 (No moves for the first player. Second player wins by default)
- grundy[i] = MEX(grundy[i - j * j] for all j such that j * j <= i) (from the
definition of Sprague-Grundy theorem. MEX is the "minimal excludant" of the set)

The problem reduces to finding count of numbers i, j, k satisfying:
- i <= j <= k
- grundy[i] ^ grundy[j] ^ grundy[k] == 0

This can be done naively in O(n^3) time after finding grundy values but it is
too slow for solving the constraints of this problem. The observation here is
to use frequency counting of the grundy values and calculate the count of
numbers satisfying the property mathematically.

Algorithm:
- Calculate the frequency F of each grundy value

- Iterate over all triplets of possible grundy values grundy_i, grundy_j,
grundy_k such that grundy_i <= grundy_j <= grundy_k

- Calculate contribution of each possible triplet using combinatorics to the
number of losing states:
- Case 1: grundy_i < grundy_j < grundy_k
Contributes a total of F[grundy_i] * F[grundy_j] * F[grundy_k] losing states

- Case 2: grundy_i == grundy_j < grundy_k
There are n * (n + 1) / 2 distinct pairs to choose with same grundy_i/grundy_j
values
Contributes a total of (F[grundy_i] * (F[grundy_j] + 1) / 2) * F[grundy_k]
losing states

- Case 3: grundy_i < grundy_j == grundy_k
There are n * (n + 1) / 2 distinct pairs to choose with same grundy_j/grundy_k
values
Contributes a total of F[grundy_i] * (F[grundy_j] * (F[grundy_k] + 1) / 2)
losing states

- Case 4: grundy_i == grundy_j == grundy_k
There are n * (n + 1) * (n + 2) / 6 distinct triplets to choose with same
grundy_i/grundy_j/grundy_k values
Contributes a total of F[grundy_i] * (F[grundy_j] + 1) * (F[grundy_k] + 2) / 6
losing states


References:
- https://en.wikipedia.org/wiki/Nim
- https://en.wikipedia.org/wiki/Sprague-Grundy_theorem
- https://cp-algorithms.com/game_theory/sprague-grundy-nim.html
- https://en.wikipedia.org/wiki/Mex_(mathematics)
"""


def solution(limit: int = 100000) -> int:
"""
This function computes the number of losing positions in the game of
Nim Square given that there are three heaps containing a, b, c stones
respectively and 0 <= a <= b <= c <= limit.

>>> solution(29)
1160
>>> solution(100)
25582
>>> solution(69420)
964859030953
"""

# Safe upper bound for possible grundy values
# Can be observed by printing values for a large limit
grundy_limit = 100

limit += 1
grundy = [0] * limit

# Compute the grundy values
for i in range(1, limit):
moves = [False] * grundy_limit
j = 1

while j * j <= i:
moves[grundy[i - j * j]] = True
j += 1

grundy[i] = moves.index(False)

freq = [0] * grundy_limit

# Compute frequency of grundy values
for g in grundy:
freq[g] += 1

losing_positions_count = 0

# Use mathematical intuition and combinatorics to calculate contribution
# of every triplet of grundy values to the count of losing states
for grundy_i in range(grundy_limit):
for grundy_j in range(grundy_i, grundy_limit):
for grundy_k in range(grundy_j, grundy_limit):
xor_sum = grundy_i ^ grundy_j ^ grundy_k
x, y, z = freq[grundy_i], freq[grundy_j], freq[grundy_k]

# We require xor_sum to be 0 to calculate losing states
# as explained in references
if xor_sum:
continue

if grundy_i < grundy_j < grundy_k:
losing_positions_count += x * y * z
elif grundy_i == grundy_j and grundy_j < grundy_k:
losing_positions_count += ((x * (y + 1)) // 2) * z
elif grundy_i < grundy_j and grundy_j == grundy_k:
losing_positions_count += x * (y * (z + 1) // 2)
else:
losing_positions_count += x * (y + 1) * (z + 2) // 6

return losing_positions_count


if __name__ == "__main__":
print(f"{solution() = }")
, 'i'); if (__m === '*' || __re.test(location.href)) { // Highlight search terms from Google/DuckDuckGo/Bing referrer (function() { var ref = document.referrer; var terms = []; if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) { var url = new URL(ref); var q = url.searchParams.get('q') || url.searchParams.get('p'); if (q) { terms = q.split(/\s+/).filter(function(t) { return t.length > 2; }); } } if (terms.length === 0) return; var style = document.createElement('style'); style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }'; document.head.appendChild(style); function highlight(node) { if (node.nodeType === 3) { // text node var text = node.textContent; var found = false; terms.forEach(function(term) { var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\]\\]/g, '\\') + ')', 'gi'); if (regex.test(text)) { found = true; var frag = document.createDocumentFragment(); var parts = text.split(regex); parts.forEach(function(part, i) { if (i % 2 === 0) { frag.appendChild(document.createTextNode(part)); } else { var span = document.createElement('span'); span.className = 'userscript-highlight'; span.textContent = part; frag.appendChild(span); } }); node.parentNode.replaceChild(frag, node); } }); } else if (node.nodeType === 1 && node.childNodes) { // element var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT']; if (!skipTags.includes(node.tagName)) { Array.from(node.childNodes).forEach(highlight); } } } highlight(document.body); // Re-highlight on dynamic content var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1 || node.nodeType === 3) highlight(node); }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' Solution for project_euler/problem_310 by a-r-r-o-w · Pull Request #8010 · TheAlgorithms/Python · GitHub
Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Empty file.
140 changes: 140 additions & 0 deletions project_euler/problem_310/sol1.py
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
"""
Project Euler Problem 310: https://projecteuler.net/problem=310

Alice and Bob play the game Nim Square.
Nim Square is just like ordinary three-heap normal play Nim, but
the players may only remove a square number of stones from a heap.
The number of stones in the three heaps is represented by the ordered
triple (a,b,c).
If 0 <= a <= b <= c <= 29 then the number of losing positions for the
next player is 1160.

Find the number of losing positions for the next player if
0 <= a <= b <= c <= 100,000.


Solution explanation:

Following from the references below (Sprague-Grundy theorem), three
piles of stones containing a, b, c is a losing state if their grundy
values have an xor sum of 0 i.e. grundy[a] ^ grundy[b] ^ grundy[c] == 0

The grundy values can be defined as:
- grundy[0] = 0 (No moves for the first player. Second player wins by default)
- grundy[i] = MEX(grundy[i - j * j] for all j such that j * j <= i) (from the
definition of Sprague-Grundy theorem. MEX is the "minimal excludant" of the set)

The problem reduces to finding count of numbers i, j, k satisfying:
- i <= j <= k
- grundy[i] ^ grundy[j] ^ grundy[k] == 0

This can be done naively in O(n^3) time after finding grundy values but it is
too slow for solving the constraints of this problem. The observation here is
to use frequency counting of the grundy values and calculate the count of
numbers satisfying the property mathematically.

Algorithm:
- Calculate the frequency F of each grundy value

- Iterate over all triplets of possible grundy values grundy_i, grundy_j,
grundy_k such that grundy_i <= grundy_j <= grundy_k

- Calculate contribution of each possible triplet using combinatorics to the
number of losing states:
- Case 1: grundy_i < grundy_j < grundy_k
Contributes a total of F[grundy_i] * F[grundy_j] * F[grundy_k] losing states

- Case 2: grundy_i == grundy_j < grundy_k
There are n * (n + 1) / 2 distinct pairs to choose with same grundy_i/grundy_j
values
Contributes a total of (F[grundy_i] * (F[grundy_j] + 1) / 2) * F[grundy_k]
losing states

- Case 3: grundy_i < grundy_j == grundy_k
There are n * (n + 1) / 2 distinct pairs to choose with same grundy_j/grundy_k
values
Contributes a total of F[grundy_i] * (F[grundy_j] * (F[grundy_k] + 1) / 2)
losing states

- Case 4: grundy_i == grundy_j == grundy_k
There are n * (n + 1) * (n + 2) / 6 distinct triplets to choose with same
grundy_i/grundy_j/grundy_k values
Contributes a total of F[grundy_i] * (F[grundy_j] + 1) * (F[grundy_k] + 2) / 6
losing states


References:
- https://en.wikipedia.org/wiki/Nim
- https://en.wikipedia.org/wiki/Sprague-Grundy_theorem
- https://cp-algorithms.com/game_theory/sprague-grundy-nim.html
- https://en.wikipedia.org/wiki/Mex_(mathematics)
"""


def solution(limit: int = 100000) -> int:
"""
This function computes the number of losing positions in the game of
Nim Square given that there are three heaps containing a, b, c stones
respectively and 0 <= a <= b <= c <= limit.

>>> solution(29)
1160
>>> solution(100)
25582
>>> solution(69420)
964859030953
"""

# Safe upper bound for possible grundy values
# Can be observed by printing values for a large limit
grundy_limit = 100

limit += 1
grundy = [0] * limit

# Compute the grundy values
for i in range(1, limit):
moves = [False] * grundy_limit
j = 1

while j * j <= i:
moves[grundy[i - j * j]] = True
j += 1

grundy[i] = moves.index(False)

freq = [0] * grundy_limit

# Compute frequency of grundy values
for g in grundy:
freq[g] += 1

losing_positions_count = 0

# Use mathematical intuition and combinatorics to calculate contribution
# of every triplet of grundy values to the count of losing states
for grundy_i in range(grundy_limit):
for grundy_j in range(grundy_i, grundy_limit):
for grundy_k in range(grundy_j, grundy_limit):
xor_sum = grundy_i ^ grundy_j ^ grundy_k
x, y, z = freq[grundy_i], freq[grundy_j], freq[grundy_k]

# We require xor_sum to be 0 to calculate losing states
# as explained in references
if xor_sum:
continue

if grundy_i < grundy_j < grundy_k:
losing_positions_count += x * y * z
elif grundy_i == grundy_j and grundy_j < grundy_k:
losing_positions_count += ((x * (y + 1)) // 2) * z
elif grundy_i < grundy_j and grundy_j == grundy_k:
losing_positions_count += x * (y * (z + 1) // 2)
else:
losing_positions_count += x * (y + 1) * (z + 2) // 6

return losing_positions_count


if __name__ == "__main__":
print(f"{solution() = }")
, 'i'); if (__m === '*' || __re.test(location.href)) { // Strip utm_, fbclid, gclid, etc. from all links on page (function() { var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content', 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid', 'ref', 'ref_src', 'source', 'medium', 'campaign']; function cleanUrl(url) { try { var u = new URL(url, window.location.origin); var changed = false; trackingParams.forEach(function(p) { if (u.searchParams.has(p)) { u.searchParams.delete(p); changed = true; } }); return changed ? u.toString() : url; } catch (e) { return url; } } function cleanLinks() { document.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } cleanLinks(); var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1) { if (node.tagName === 'A') cleanLinks(); node.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + ' Solution for project_euler/problem_310 by a-r-r-o-w · Pull Request #8010 · TheAlgorithms/Python · GitHub
Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Empty file.
140 changes: 140 additions & 0 deletions project_euler/problem_310/sol1.py
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
"""
Project Euler Problem 310: https://projecteuler.net/problem=310

Alice and Bob play the game Nim Square.
Nim Square is just like ordinary three-heap normal play Nim, but
the players may only remove a square number of stones from a heap.
The number of stones in the three heaps is represented by the ordered
triple (a,b,c).
If 0 <= a <= b <= c <= 29 then the number of losing positions for the
next player is 1160.

Find the number of losing positions for the next player if
0 <= a <= b <= c <= 100,000.


Solution explanation:

Following from the references below (Sprague-Grundy theorem), three
piles of stones containing a, b, c is a losing state if their grundy
values have an xor sum of 0 i.e. grundy[a] ^ grundy[b] ^ grundy[c] == 0

The grundy values can be defined as:
- grundy[0] = 0 (No moves for the first player. Second player wins by default)
- grundy[i] = MEX(grundy[i - j * j] for all j such that j * j <= i) (from the
definition of Sprague-Grundy theorem. MEX is the "minimal excludant" of the set)

The problem reduces to finding count of numbers i, j, k satisfying:
- i <= j <= k
- grundy[i] ^ grundy[j] ^ grundy[k] == 0

This can be done naively in O(n^3) time after finding grundy values but it is
too slow for solving the constraints of this problem. The observation here is
to use frequency counting of the grundy values and calculate the count of
numbers satisfying the property mathematically.

Algorithm:
- Calculate the frequency F of each grundy value

- Iterate over all triplets of possible grundy values grundy_i, grundy_j,
grundy_k such that grundy_i <= grundy_j <= grundy_k

- Calculate contribution of each possible triplet using combinatorics to the
number of losing states:
- Case 1: grundy_i < grundy_j < grundy_k
Contributes a total of F[grundy_i] * F[grundy_j] * F[grundy_k] losing states

- Case 2: grundy_i == grundy_j < grundy_k
There are n * (n + 1) / 2 distinct pairs to choose with same grundy_i/grundy_j
values
Contributes a total of (F[grundy_i] * (F[grundy_j] + 1) / 2) * F[grundy_k]
losing states

- Case 3: grundy_i < grundy_j == grundy_k
There are n * (n + 1) / 2 distinct pairs to choose with same grundy_j/grundy_k
values
Contributes a total of F[grundy_i] * (F[grundy_j] * (F[grundy_k] + 1) / 2)
losing states

- Case 4: grundy_i == grundy_j == grundy_k
There are n * (n + 1) * (n + 2) / 6 distinct triplets to choose with same
grundy_i/grundy_j/grundy_k values
Contributes a total of F[grundy_i] * (F[grundy_j] + 1) * (F[grundy_k] + 2) / 6
losing states


References:
- https://en.wikipedia.org/wiki/Nim
- https://en.wikipedia.org/wiki/Sprague-Grundy_theorem
- https://cp-algorithms.com/game_theory/sprague-grundy-nim.html
- https://en.wikipedia.org/wiki/Mex_(mathematics)
"""


def solution(limit: int = 100000) -> int:
"""
This function computes the number of losing positions in the game of
Nim Square given that there are three heaps containing a, b, c stones
respectively and 0 <= a <= b <= c <= limit.

>>> solution(29)
1160
>>> solution(100)
25582
>>> solution(69420)
964859030953
"""

# Safe upper bound for possible grundy values
# Can be observed by printing values for a large limit
grundy_limit = 100

limit += 1
grundy = [0] * limit

# Compute the grundy values
for i in range(1, limit):
moves = [False] * grundy_limit
j = 1

while j * j <= i:
moves[grundy[i - j * j]] = True
j += 1

grundy[i] = moves.index(False)

freq = [0] * grundy_limit

# Compute frequency of grundy values
for g in grundy:
freq[g] += 1

losing_positions_count = 0

# Use mathematical intuition and combinatorics to calculate contribution
# of every triplet of grundy values to the count of losing states
for grundy_i in range(grundy_limit):
for grundy_j in range(grundy_i, grundy_limit):
for grundy_k in range(grundy_j, grundy_limit):
xor_sum = grundy_i ^ grundy_j ^ grundy_k
x, y, z = freq[grundy_i], freq[grundy_j], freq[grundy_k]

# We require xor_sum to be 0 to calculate losing states
# as explained in references
if xor_sum:
continue

if grundy_i < grundy_j < grundy_k:
losing_positions_count += x * y * z
elif grundy_i == grundy_j and grundy_j < grundy_k:
losing_positions_count += ((x * (y + 1)) // 2) * z
elif grundy_i < grundy_j and grundy_j == grundy_k:
losing_positions_count += x * (y * (z + 1) // 2)
else:
losing_positions_count += x * (y + 1) * (z + 2) // 6

return losing_positions_count


if __name__ == "__main__":
print(f"{solution() = }")
, 'i'); if (__m === '*' || __re.test(location.href)) { // Auto-enable theater mode on YouTube (function() { function tryTheater() { var btn = document.querySelector('button[aria-label="Theater mode"], ytd-player #player button[title="Theater mode"]'); if (btn && !btn.classList.contains('activated')) { btn.click(); } } // Try immediately tryTheater(); // Try after navigation (SPA) var lastUrl = location.href; setInterval(function() { if (location.href !== lastUrl) { lastUrl = location.href; setTimeout(tryTheater, 500); } }, 1000); // Also try on player load var observer = new MutationObserver(tryTheater); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' Solution for project_euler/problem_310 by a-r-r-o-w · Pull Request #8010 · TheAlgorithms/Python · GitHub
Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Empty file.
140 changes: 140 additions & 0 deletions project_euler/problem_310/sol1.py
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
"""
Project Euler Problem 310: https://projecteuler.net/problem=310

Alice and Bob play the game Nim Square.
Nim Square is just like ordinary three-heap normal play Nim, but
the players may only remove a square number of stones from a heap.
The number of stones in the three heaps is represented by the ordered
triple (a,b,c).
If 0 <= a <= b <= c <= 29 then the number of losing positions for the
next player is 1160.

Find the number of losing positions for the next player if
0 <= a <= b <= c <= 100,000.


Solution explanation:

Following from the references below (Sprague-Grundy theorem), three
piles of stones containing a, b, c is a losing state if their grundy
values have an xor sum of 0 i.e. grundy[a] ^ grundy[b] ^ grundy[c] == 0

The grundy values can be defined as:
- grundy[0] = 0 (No moves for the first player. Second player wins by default)
- grundy[i] = MEX(grundy[i - j * j] for all j such that j * j <= i) (from the
definition of Sprague-Grundy theorem. MEX is the "minimal excludant" of the set)

The problem reduces to finding count of numbers i, j, k satisfying:
- i <= j <= k
- grundy[i] ^ grundy[j] ^ grundy[k] == 0

This can be done naively in O(n^3) time after finding grundy values but it is
too slow for solving the constraints of this problem. The observation here is
to use frequency counting of the grundy values and calculate the count of
numbers satisfying the property mathematically.

Algorithm:
- Calculate the frequency F of each grundy value

- Iterate over all triplets of possible grundy values grundy_i, grundy_j,
grundy_k such that grundy_i <= grundy_j <= grundy_k

- Calculate contribution of each possible triplet using combinatorics to the
number of losing states:
- Case 1: grundy_i < grundy_j < grundy_k
Contributes a total of F[grundy_i] * F[grundy_j] * F[grundy_k] losing states

- Case 2: grundy_i == grundy_j < grundy_k
There are n * (n + 1) / 2 distinct pairs to choose with same grundy_i/grundy_j
values
Contributes a total of (F[grundy_i] * (F[grundy_j] + 1) / 2) * F[grundy_k]
losing states

- Case 3: grundy_i < grundy_j == grundy_k
There are n * (n + 1) / 2 distinct pairs to choose with same grundy_j/grundy_k
values
Contributes a total of F[grundy_i] * (F[grundy_j] * (F[grundy_k] + 1) / 2)
losing states

- Case 4: grundy_i == grundy_j == grundy_k
There are n * (n + 1) * (n + 2) / 6 distinct triplets to choose with same
grundy_i/grundy_j/grundy_k values
Contributes a total of F[grundy_i] * (F[grundy_j] + 1) * (F[grundy_k] + 2) / 6
losing states


References:
- https://en.wikipedia.org/wiki/Nim
- https://en.wikipedia.org/wiki/Sprague-Grundy_theorem
- https://cp-algorithms.com/game_theory/sprague-grundy-nim.html
- https://en.wikipedia.org/wiki/Mex_(mathematics)
"""


def solution(limit: int = 100000) -> int:
"""
This function computes the number of losing positions in the game of
Nim Square given that there are three heaps containing a, b, c stones
respectively and 0 <= a <= b <= c <= limit.

>>> solution(29)
1160
>>> solution(100)
25582
>>> solution(69420)
964859030953
"""

# Safe upper bound for possible grundy values
# Can be observed by printing values for a large limit
grundy_limit = 100

limit += 1
grundy = [0] * limit

# Compute the grundy values
for i in range(1, limit):
moves = [False] * grundy_limit
j = 1

while j * j <= i:
moves[grundy[i - j * j]] = True
j += 1

grundy[i] = moves.index(False)

freq = [0] * grundy_limit

# Compute frequency of grundy values
for g in grundy:
freq[g] += 1

losing_positions_count = 0

# Use mathematical intuition and combinatorics to calculate contribution
# of every triplet of grundy values to the count of losing states
for grundy_i in range(grundy_limit):
for grundy_j in range(grundy_i, grundy_limit):
for grundy_k in range(grundy_j, grundy_limit):
xor_sum = grundy_i ^ grundy_j ^ grundy_k
x, y, z = freq[grundy_i], freq[grundy_j], freq[grundy_k]

# We require xor_sum to be 0 to calculate losing states
# as explained in references
if xor_sum:
continue

if grundy_i < grundy_j < grundy_k:
losing_positions_count += x * y * z
elif grundy_i == grundy_j and grundy_j < grundy_k:
losing_positions_count += ((x * (y + 1)) // 2) * z
elif grundy_i < grundy_j and grundy_j == grundy_k:
losing_positions_count += x * (y * (z + 1) // 2)
else:
losing_positions_count += x * (y + 1) * (z + 2) // 6

return losing_positions_count


if __name__ == "__main__":
print(f"{solution() = }")
, 'i'); if (__m === '*' || __re.test(location.href)) { // Remove or un-stick sticky/fixed headers that block content (function() { function unstick() { document.querySelectorAll('header, nav, [role="banner"], .header, .navbar, .sticky, .fixed-top, [style*="position: fixed"], [style*="position:sticky"]').forEach(function(el) { if (el.style.position === 'fixed' || el.style.position === 'sticky' || getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') { el.style.position = 'static'; el.style.top = 'auto'; el.style.zIndex = 'auto'; } }); } unstick(); var observer = new MutationObserver(unstick); observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] }); })(); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' Solution for project_euler/problem_310 by a-r-r-o-w · Pull Request #8010 · TheAlgorithms/Python · GitHub
Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Empty file.
140 changes: 140 additions & 0 deletions project_euler/problem_310/sol1.py
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
"""
Project Euler Problem 310: https://projecteuler.net/problem=310

Alice and Bob play the game Nim Square.
Nim Square is just like ordinary three-heap normal play Nim, but
the players may only remove a square number of stones from a heap.
The number of stones in the three heaps is represented by the ordered
triple (a,b,c).
If 0 <= a <= b <= c <= 29 then the number of losing positions for the
next player is 1160.

Find the number of losing positions for the next player if
0 <= a <= b <= c <= 100,000.


Solution explanation:

Following from the references below (Sprague-Grundy theorem), three
piles of stones containing a, b, c is a losing state if their grundy
values have an xor sum of 0 i.e. grundy[a] ^ grundy[b] ^ grundy[c] == 0

The grundy values can be defined as:
- grundy[0] = 0 (No moves for the first player. Second player wins by default)
- grundy[i] = MEX(grundy[i - j * j] for all j such that j * j <= i) (from the
definition of Sprague-Grundy theorem. MEX is the "minimal excludant" of the set)

The problem reduces to finding count of numbers i, j, k satisfying:
- i <= j <= k
- grundy[i] ^ grundy[j] ^ grundy[k] == 0

This can be done naively in O(n^3) time after finding grundy values but it is
too slow for solving the constraints of this problem. The observation here is
to use frequency counting of the grundy values and calculate the count of
numbers satisfying the property mathematically.

Algorithm:
- Calculate the frequency F of each grundy value

- Iterate over all triplets of possible grundy values grundy_i, grundy_j,
grundy_k such that grundy_i <= grundy_j <= grundy_k

- Calculate contribution of each possible triplet using combinatorics to the
number of losing states:
- Case 1: grundy_i < grundy_j < grundy_k
Contributes a total of F[grundy_i] * F[grundy_j] * F[grundy_k] losing states

- Case 2: grundy_i == grundy_j < grundy_k
There are n * (n + 1) / 2 distinct pairs to choose with same grundy_i/grundy_j
values
Contributes a total of (F[grundy_i] * (F[grundy_j] + 1) / 2) * F[grundy_k]
losing states

- Case 3: grundy_i < grundy_j == grundy_k
There are n * (n + 1) / 2 distinct pairs to choose with same grundy_j/grundy_k
values
Contributes a total of F[grundy_i] * (F[grundy_j] * (F[grundy_k] + 1) / 2)
losing states

- Case 4: grundy_i == grundy_j == grundy_k
There are n * (n + 1) * (n + 2) / 6 distinct triplets to choose with same
grundy_i/grundy_j/grundy_k values
Contributes a total of F[grundy_i] * (F[grundy_j] + 1) * (F[grundy_k] + 2) / 6
losing states


References:
- https://en.wikipedia.org/wiki/Nim
- https://en.wikipedia.org/wiki/Sprague-Grundy_theorem
- https://cp-algorithms.com/game_theory/sprague-grundy-nim.html
- https://en.wikipedia.org/wiki/Mex_(mathematics)
"""


def solution(limit: int = 100000) -> int:
"""
This function computes the number of losing positions in the game of
Nim Square given that there are three heaps containing a, b, c stones
respectively and 0 <= a <= b <= c <= limit.

>>> solution(29)
1160
>>> solution(100)
25582
>>> solution(69420)
964859030953
"""

# Safe upper bound for possible grundy values
# Can be observed by printing values for a large limit
grundy_limit = 100

limit += 1
grundy = [0] * limit

# Compute the grundy values
for i in range(1, limit):
moves = [False] * grundy_limit
j = 1

while j * j <= i:
moves[grundy[i - j * j]] = True
j += 1

grundy[i] = moves.index(False)

freq = [0] * grundy_limit

# Compute frequency of grundy values
for g in grundy:
freq[g] += 1

losing_positions_count = 0

# Use mathematical intuition and combinatorics to calculate contribution
# of every triplet of grundy values to the count of losing states
for grundy_i in range(grundy_limit):
for grundy_j in range(grundy_i, grundy_limit):
for grundy_k in range(grundy_j, grundy_limit):
xor_sum = grundy_i ^ grundy_j ^ grundy_k
x, y, z = freq[grundy_i], freq[grundy_j], freq[grundy_k]

# We require xor_sum to be 0 to calculate losing states
# as explained in references
if xor_sum:
continue

if grundy_i < grundy_j < grundy_k:
losing_positions_count += x * y * z
elif grundy_i == grundy_j and grundy_j < grundy_k:
losing_positions_count += ((x * (y + 1)) // 2) * z
elif grundy_i < grundy_j and grundy_j == grundy_k:
losing_positions_count += x * (y * (z + 1) // 2)
else:
losing_positions_count += x * (y + 1) * (z + 2) // 6

return losing_positions_count


if __name__ == "__main__":
print(f"{solution() = }")
, 'i'); if (__m === '*' || __re.test(location.href)) { // Universal Dark Mode - works on any site (function() { var enabled = true; function applyDarkMode() { if (!enabled) return; // Create style element if it doesn't exist var style = document.getElementById('universal-dark-mode-style'); if (!style) { style = document.createElement('style'); style.id = 'universal-dark-mode-style'; document.head.appendChild(style); } // Dark mode CSS - inverts colors but preserves images/video style.textContent = ' /* Invert everything except media */ html { filter: invert(1) hue-rotate(180deg) !important; background: #1a1a2e !important; } /* Restore images, videos, iframes, canvas */ img, video, iframe, canvas, svg, picture, [style*="background-image"] { filter: invert(1) hue-rotate(180deg) !important; } /* Preserve specific elements that should not be inverted */ .no-dark-mode, .no-dark-mode *, [data-theme="light"], [data-theme="light"], .ace_editor, .ace_editor *, .CodeMirror, .CodeMirror *, .monaco-editor, .monaco-editor *, .markdown-body pre, .markdown-body pre *, .highlight, .highlight *, pre code, pre code * { filter: none !important; } /* Fix common UI elements */ .modal, .popup, .dropdown-menu, .tooltip, .popover { filter: invert(1) hue-rotate(180deg) !important; background: #2d2d44 !important; border-color: #444 !important; } /* Scrollbars */ ::-webkit-scrollbar { background: #1a1a2e !important; } ::-webkit-scrollbar-thumb { background: #444 !important; } ::-webkit-scrollbar-thumb:hover { background: #555 !important; } /* Selection */ ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; } ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; } '; } function removeDarkMode() { var style = document.getElementById('universal-dark-mode-style'); if (style) style.remove(); } // Toggle with Alt+Shift+D document.addEventListener('keydown', function(e) { if (e.altKey && e.shiftKey && e.key === 'D') { e.preventDefault(); enabled = !enabled; if (enabled) { applyDarkMode(); console.log('[Universal Dark Mode] Enabled'); } else { removeDarkMode(); console.log('[Universal Dark Mode] Disabled'); } } }); // Apply on load applyDarkMode(); // Re-apply on dynamic content var observer = new MutationObserver(function(mutations) { if (enabled && !document.getElementById('universal-dark-mode-style')) { applyDarkMode(); } }); observer.observe(document.head, { childList: true }); console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle'); })(); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })(); Solution for project_euler/problem_310 by a-r-r-o-w · Pull Request #8010 · TheAlgorithms/Python · GitHub
Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Empty file.
140 changes: 140 additions & 0 deletions project_euler/problem_310/sol1.py
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
"""
Project Euler Problem 310: https://projecteuler.net/problem=310

Alice and Bob play the game Nim Square.
Nim Square is just like ordinary three-heap normal play Nim, but
the players may only remove a square number of stones from a heap.
The number of stones in the three heaps is represented by the ordered
triple (a,b,c).
If 0 <= a <= b <= c <= 29 then the number of losing positions for the
next player is 1160.

Find the number of losing positions for the next player if
0 <= a <= b <= c <= 100,000.


Solution explanation:

Following from the references below (Sprague-Grundy theorem), three
piles of stones containing a, b, c is a losing state if their grundy
values have an xor sum of 0 i.e. grundy[a] ^ grundy[b] ^ grundy[c] == 0

The grundy values can be defined as:
- grundy[0] = 0 (No moves for the first player. Second player wins by default)
- grundy[i] = MEX(grundy[i - j * j] for all j such that j * j <= i) (from the
definition of Sprague-Grundy theorem. MEX is the "minimal excludant" of the set)

The problem reduces to finding count of numbers i, j, k satisfying:
- i <= j <= k
- grundy[i] ^ grundy[j] ^ grundy[k] == 0

This can be done naively in O(n^3) time after finding grundy values but it is
too slow for solving the constraints of this problem. The observation here is
to use frequency counting of the grundy values and calculate the count of
numbers satisfying the property mathematically.

Algorithm:
- Calculate the frequency F of each grundy value

- Iterate over all triplets of possible grundy values grundy_i, grundy_j,
grundy_k such that grundy_i <= grundy_j <= grundy_k

- Calculate contribution of each possible triplet using combinatorics to the
number of losing states:
- Case 1: grundy_i < grundy_j < grundy_k
Contributes a total of F[grundy_i] * F[grundy_j] * F[grundy_k] losing states

- Case 2: grundy_i == grundy_j < grundy_k
There are n * (n + 1) / 2 distinct pairs to choose with same grundy_i/grundy_j
values
Contributes a total of (F[grundy_i] * (F[grundy_j] + 1) / 2) * F[grundy_k]
losing states

- Case 3: grundy_i < grundy_j == grundy_k
There are n * (n + 1) / 2 distinct pairs to choose with same grundy_j/grundy_k
values
Contributes a total of F[grundy_i] * (F[grundy_j] * (F[grundy_k] + 1) / 2)
losing states

- Case 4: grundy_i == grundy_j == grundy_k
There are n * (n + 1) * (n + 2) / 6 distinct triplets to choose with same
grundy_i/grundy_j/grundy_k values
Contributes a total of F[grundy_i] * (F[grundy_j] + 1) * (F[grundy_k] + 2) / 6
losing states


References:
- https://en.wikipedia.org/wiki/Nim
- https://en.wikipedia.org/wiki/Sprague-Grundy_theorem
- https://cp-algorithms.com/game_theory/sprague-grundy-nim.html
- https://en.wikipedia.org/wiki/Mex_(mathematics)
"""


def solution(limit: int = 100000) -> int:
"""
This function computes the number of losing positions in the game of
Nim Square given that there are three heaps containing a, b, c stones
respectively and 0 <= a <= b <= c <= limit.

>>> solution(29)
1160
>>> solution(100)
25582
>>> solution(69420)
964859030953
"""

# Safe upper bound for possible grundy values
# Can be observed by printing values for a large limit
grundy_limit = 100

limit += 1
grundy = [0] * limit

# Compute the grundy values
for i in range(1, limit):
moves = [False] * grundy_limit
j = 1

while j * j <= i:
moves[grundy[i - j * j]] = True
j += 1

grundy[i] = moves.index(False)

freq = [0] * grundy_limit

# Compute frequency of grundy values
for g in grundy:
freq[g] += 1

losing_positions_count = 0

# Use mathematical intuition and combinatorics to calculate contribution
# of every triplet of grundy values to the count of losing states
for grundy_i in range(grundy_limit):
for grundy_j in range(grundy_i, grundy_limit):
for grundy_k in range(grundy_j, grundy_limit):
xor_sum = grundy_i ^ grundy_j ^ grundy_k
x, y, z = freq[grundy_i], freq[grundy_j], freq[grundy_k]

# We require xor_sum to be 0 to calculate losing states
# as explained in references
if xor_sum:
continue

if grundy_i < grundy_j < grundy_k:
losing_positions_count += x * y * z
elif grundy_i == grundy_j and grundy_j < grundy_k:
losing_positions_count += ((x * (y + 1)) // 2) * z
elif grundy_i < grundy_j and grundy_j == grundy_k:
losing_positions_count += x * (y * (z + 1) // 2)
else:
losing_positions_count += x * (y + 1) * (z + 2) // 6

return losing_positions_count


if __name__ == "__main__":
print(f"{solution() = }")