From eef3b7e2259f650faa76a79e9648d87f4b2b16e0 Mon Sep 17 00:00:00 2001 From: Bjorn Date: Tue, 16 Jun 2020 03:01:25 +0200 Subject: [PATCH 1/7] Added template for modulo calculations --- pyrival/algebra/mod_template.py | 81 +++++++++++++++++++++++++++++++++ 1 file changed, 81 insertions(+) create mode 100644 pyrival/algebra/mod_template.py diff --git a/pyrival/algebra/mod_template.py b/pyrival/algebra/mod_template.py new file mode 100644 index 00000000..73203ca4 --- /dev/null +++ b/pyrival/algebra/mod_template.py @@ -0,0 +1,81 @@ +""" +This template is useful for problems involving a prime modulo. +The template contains a fast function for calculating a * b % MOD, +as well as precalculating factorial, inverse factorial, modular inverse +for all numbers <= maxN in O(maxN) time. + +With this template, one can for example quickly calculate n choose k mod MOD, +or calculate matrix multiplication mod MOD. +""" + +def fast_modder(MOD): + """ Returns function modmul(a,b) that quickly calculates a * b % MOD, assuming 0 <= a,b < MOD """ + import sys, platform + impl = platform.python_implementation() + maxs = sys.maxsize + if 'PyPy' in impl and MOD <= maxs and MOD ** 2 > maxs: + import __pypy__ + intsub = __pypy__.intop.int_sub + intmul = __pypy__.intop.int_mul + intmulmod = __pypy__.intop.int_mulmod + if MOD < 2**30: + MODINV = 1.0 / MOD + def modmul(a, b): + x = intsub(intmul(a,b), intmul(MOD, int(MODINV * a * b))) + return x - MOD if x >= MOD else (x if x >= 0 else x + MOD) + else: + def modmul(a, b): + return intmulmod(a, b, MOD) + else: + def modmul(a, b): + return a * b % MOD + return modmul + + +""" Precalculate factorial, modular inverse of factorial and modular inverse """ +MOD = 10 ** 9 + 7 # needs to be prime! +maxN = 10 ** 6 +modmul = fast_modder(MOD) + +def mod_precalc(): + """ Calculates fac, inv_fac and mod_inv for i <= maxN in O(maxN) time """ + assert maxN <= MOD + + fac = [1] * maxN + for i in range(2, maxN): + fac[i] = modmul(fac[i - 1], i) + + inv_fac = [pow(fac[-1], MOD - 2, MOD)] * maxN + for i in reversed(range(1, maxN)): + inv_fac[i - 1] = modmul(inv_fac[i], i) + + inv_mod = [modmul(fac[i], inv_fac[i - 1]) for i in range(maxN)] + + return fac, inv_fac, inv_mod + +fac, inv_fac, inv_mod = mod_precalc() + +""" Useful functions involving modulo """ + +def choose(n,k): + """ Calculate n choose k in O(1) time """ + if k < 0 or k > n: + return 0 + return modmul(modmul(fac[n], invfac[k]), invfac[n-k]) + +def redu(x): + return x if x < MOD else x - MOD + +def matrix_modmul(A, B): + """ Multiplies matrices A and B, assuming 0 <= A[i][j], B[i][j] < MOD """ + assert len(A[0]) == len(B) + C = [] + for Ai in A: + tmp = [0] * len(B[0]) + for k in range(len(B)): + Aik = Ai[k] + Bk = B[k] + for j in range(len(Bk)): + tmp[j] = redu(tmp[j], modmul(Aik, Bk[j])) + C.append(tmp) + return C From 54ea14c28e1ac4f6a032c4556b025253316eea8a Mon Sep 17 00:00:00 2001 From: Bjorn Date: Tue, 16 Jun 2020 03:32:50 +0200 Subject: [PATCH 2/7] Fixed erroneous inequality --- pyrival/algebra/mod_template.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pyrival/algebra/mod_template.py b/pyrival/algebra/mod_template.py index 73203ca4..a6973d7c 100644 --- a/pyrival/algebra/mod_template.py +++ b/pyrival/algebra/mod_template.py @@ -2,7 +2,7 @@ This template is useful for problems involving a prime modulo. The template contains a fast function for calculating a * b % MOD, as well as precalculating factorial, inverse factorial, modular inverse -for all numbers <= maxN in O(maxN) time. +for all integers < maxN in O(maxN) time. With this template, one can for example quickly calculate n choose k mod MOD, or calculate matrix multiplication mod MOD. @@ -38,7 +38,7 @@ def modmul(a, b): modmul = fast_modder(MOD) def mod_precalc(): - """ Calculates fac, inv_fac and mod_inv for i <= maxN in O(maxN) time """ + """ Calculates fac, inv_fac and mod_inv for i < maxN in O(maxN) time """ assert maxN <= MOD fac = [1] * maxN From ccb52100faaafc463b79f9739efa1a3e18e3379b Mon Sep 17 00:00:00 2001 From: Bjorn Date: Sat, 23 Jan 2021 19:06:29 +0100 Subject: [PATCH 3/7] General improvements --- pyrival/algebra/mod_template.py | 23 +++++++++-------------- 1 file changed, 9 insertions(+), 14 deletions(-) diff --git a/pyrival/algebra/mod_template.py b/pyrival/algebra/mod_template.py index a6973d7c..2d59127a 100644 --- a/pyrival/algebra/mod_template.py +++ b/pyrival/algebra/mod_template.py @@ -3,7 +3,6 @@ The template contains a fast function for calculating a * b % MOD, as well as precalculating factorial, inverse factorial, modular inverse for all integers < maxN in O(maxN) time. - With this template, one can for example quickly calculate n choose k mod MOD, or calculate matrix multiplication mod MOD. """ @@ -18,17 +17,16 @@ def fast_modder(MOD): intsub = __pypy__.intop.int_sub intmul = __pypy__.intop.int_mul intmulmod = __pypy__.intop.int_mulmod - if MOD < 2**30: + if MOD < 2**30 - 1000: MODINV = 1.0 / MOD - def modmul(a, b): - x = intsub(intmul(a,b), intmul(MOD, int(MODINV * a * b))) - return x - MOD if x >= MOD else (x if x >= 0 else x + MOD) + def modmul(a, b, c=0): + return (intsub(intmul(a,b), intmul(MOD, int(MODINV * a * b))) + c) % MOD else: - def modmul(a, b): - return intmulmod(a, b, MOD) + def modmul(a, b, c=0): + return (intmulmod(a, b, MOD) + c) % MOD else: - def modmul(a, b): - return a * b % MOD + def modmul(a, b, c=0): + return (a * b + c) % MOD return modmul @@ -49,7 +47,7 @@ def mod_precalc(): for i in reversed(range(1, maxN)): inv_fac[i - 1] = modmul(inv_fac[i], i) - inv_mod = [modmul(fac[i], inv_fac[i - 1]) for i in range(maxN)] + inv_mod = [modmul(inv_fac[i], fac[i - 1]) for i in range(maxN)] return fac, inv_fac, inv_mod @@ -63,9 +61,6 @@ def choose(n,k): return 0 return modmul(modmul(fac[n], invfac[k]), invfac[n-k]) -def redu(x): - return x if x < MOD else x - MOD - def matrix_modmul(A, B): """ Multiplies matrices A and B, assuming 0 <= A[i][j], B[i][j] < MOD """ assert len(A[0]) == len(B) @@ -76,6 +71,6 @@ def matrix_modmul(A, B): Aik = Ai[k] Bk = B[k] for j in range(len(Bk)): - tmp[j] = redu(tmp[j], modmul(Aik, Bk[j])) + tmp[j] = modmul(Aik, Bk[j], tmp[j]) C.append(tmp) return C From 4766516abb17890e1c5ed70bb12fc8e75c173e63 Mon Sep 17 00:00:00 2001 From: Bjorn Date: Sat, 23 Jan 2021 19:13:23 +0100 Subject: [PATCH 4/7] Fixed description --- pyrival/algebra/mod_template.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyrival/algebra/mod_template.py b/pyrival/algebra/mod_template.py index 2d59127a..42a47b38 100644 --- a/pyrival/algebra/mod_template.py +++ b/pyrival/algebra/mod_template.py @@ -8,7 +8,7 @@ """ def fast_modder(MOD): - """ Returns function modmul(a,b) that quickly calculates a * b % MOD, assuming 0 <= a,b < MOD """ + """ Returns function modmul(a,b,c=0) that quickly calculates (a * b + c) % MOD, assuming 0 <= a,b < MOD """ import sys, platform impl = platform.python_implementation() maxs = sys.maxsize From 0a990ea0a1aaab1306383e84ef03b9327f12780f Mon Sep 17 00:00:00 2001 From: Bjorn Date: Tue, 20 Apr 2021 20:23:53 +0200 Subject: [PATCH 5/7] Changed formatting, comments and variable names. --- pyrival/algebra/mod_template.py | 49 +++++++++++++++++---------------- 1 file changed, 26 insertions(+), 23 deletions(-) diff --git a/pyrival/algebra/mod_template.py b/pyrival/algebra/mod_template.py index 42a47b38..ffef7019 100644 --- a/pyrival/algebra/mod_template.py +++ b/pyrival/algebra/mod_template.py @@ -1,14 +1,17 @@ """ -This template is useful for problems involving a prime modulo. -The template contains a fast function for calculating a * b % MOD, -as well as precalculating factorial, inverse factorial, modular inverse -for all integers < maxN in O(maxN) time. -With this template, one can for example quickly calculate n choose k mod MOD, -or calculate matrix multiplication mod MOD. +A template for usefull stuff involving prime modulo. It contains: +1. Fast calculation of (a * b + c) % MOD (Especially usefull for PyPy users on windows). +2. Calculation of factorial, inverse factorial and modular inverse + for all integers < maxN in O(maxN) time. +3. Calculate n choose k in O(1) time using precalculated fac. and inv. fac. +4. Multiply matrices mod MOD. """ +MOD = 10 ** 9 + 7 # needs to be prime! +maxN = 10 ** 6 # needs to be <= MOD + def fast_modder(MOD): - """ Returns function modmul(a,b,c=0) that quickly calculates (a * b + c) % MOD, assuming 0 <= a,b < MOD """ + """ Returns a function modmul(a,b,c=0) that quickly calculates (a * b + c) % MOD, assuming 0 <= a,b < MOD """ import sys, platform impl = platform.python_implementation() maxs = sys.maxsize @@ -29,37 +32,37 @@ def modmul(a, b, c=0): return (a * b + c) % MOD return modmul - -""" Precalculate factorial, modular inverse of factorial and modular inverse """ -MOD = 10 ** 9 + 7 # needs to be prime! -maxN = 10 ** 6 modmul = fast_modder(MOD) -def mod_precalc(): - """ Calculates fac, inv_fac and mod_inv for i < maxN in O(maxN) time """ - assert maxN <= MOD + +""" Precalculate factorial, inverse factorial and modular inverse """ + +def mod_precalc(n): + """ Calculates fac, inv_fac and (modular) inv for i < n in O(n) time """ + assert n <= MOD - fac = [1] * maxN - for i in range(2, maxN): + fac = [1] * n + for i in range(2, n): fac[i] = modmul(fac[i - 1], i) - inv_fac = [pow(fac[-1], MOD - 2, MOD)] * maxN - for i in reversed(range(1, maxN)): + inv_fac = [pow(fac[-1], MOD - 2, MOD)] * n + for i in reversed(range(1, n)): inv_fac[i - 1] = modmul(inv_fac[i], i) - inv_mod = [modmul(inv_fac[i], fac[i - 1]) for i in range(maxN)] + inv = [modmul(inv_fac[i], fac[i - 1]) for i in range(n)] + + return fac, inv_fac, inv - return fac, inv_fac, inv_mod +fac, inv_fac, inv = mod_precalc(maxN) -fac, inv_fac, inv_mod = mod_precalc() """ Useful functions involving modulo """ -def choose(n,k): +def choose(n, k): """ Calculate n choose k in O(1) time """ if k < 0 or k > n: return 0 - return modmul(modmul(fac[n], invfac[k]), invfac[n-k]) + return modmul(modmul(fac[n], fac_inv[k]), fac_inv[n - k]) def matrix_modmul(A, B): """ Multiplies matrices A and B, assuming 0 <= A[i][j], B[i][j] < MOD """ From 138687a6d0313f13f3351c132e46fedfa7ac52ac Mon Sep 17 00:00:00 2001 From: pajenegod Date: Tue, 6 Oct 2026 04:41:20 +0200 Subject: [PATCH 6/7] Modernized old PR --- pyrival/algebra/mod_template.py | 37 ++++++--------------------------- 1 file changed, 6 insertions(+), 31 deletions(-) diff --git a/pyrival/algebra/mod_template.py b/pyrival/algebra/mod_template.py index ffef7019..3412d06b 100644 --- a/pyrival/algebra/mod_template.py +++ b/pyrival/algebra/mod_template.py @@ -1,39 +1,14 @@ """ A template for usefull stuff involving prime modulo. It contains: -1. Fast calculation of (a * b + c) % MOD (Especially usefull for PyPy users on windows). -2. Calculation of factorial, inverse factorial and modular inverse +1. Calculation of factorial, inverse factorial and modular inverse for all integers < maxN in O(maxN) time. -3. Calculate n choose k in O(1) time using precalculated fac. and inv. fac. -4. Multiply matrices mod MOD. +2. Calculate n choose k in O(1) time using precalculated fac and inv_fac. +3. Multiply matrices mod MOD. """ MOD = 10 ** 9 + 7 # needs to be prime! maxN = 10 ** 6 # needs to be <= MOD - -def fast_modder(MOD): - """ Returns a function modmul(a,b,c=0) that quickly calculates (a * b + c) % MOD, assuming 0 <= a,b < MOD """ - import sys, platform - impl = platform.python_implementation() - maxs = sys.maxsize - if 'PyPy' in impl and MOD <= maxs and MOD ** 2 > maxs: - import __pypy__ - intsub = __pypy__.intop.int_sub - intmul = __pypy__.intop.int_mul - intmulmod = __pypy__.intop.int_mulmod - if MOD < 2**30 - 1000: - MODINV = 1.0 / MOD - def modmul(a, b, c=0): - return (intsub(intmul(a,b), intmul(MOD, int(MODINV * a * b))) + c) % MOD - else: - def modmul(a, b, c=0): - return (intmulmod(a, b, MOD) + c) % MOD - else: - def modmul(a, b, c=0): - return (a * b + c) % MOD - return modmul - -modmul = fast_modder(MOD) - +modmul = lambda a,b,c=0:(a*b + c) % MOD """ Precalculate factorial, inverse factorial and modular inverse """ @@ -62,10 +37,10 @@ def choose(n, k): """ Calculate n choose k in O(1) time """ if k < 0 or k > n: return 0 - return modmul(modmul(fac[n], fac_inv[k]), fac_inv[n - k]) + return modmul(modmul(fac[n], inv_fac[k]), inv_fac[n - k]) def matrix_modmul(A, B): - """ Multiplies matrices A and B, assuming 0 <= A[i][j], B[i][j] < MOD """ + """ Multiplies matrices A and B mod MOD""" assert len(A[0]) == len(B) C = [] for Ai in A: From 0742751446688f7b9b6d788e13353681ec0b6739 Mon Sep 17 00:00:00 2001 From: pajenegod Date: Tue, 6 Oct 2026 05:01:08 +0200 Subject: [PATCH 7/7] Add the two decompositions --- .../centriod_shallowest_decomposition.py | 114 ++++++++++++++++++ 1 file changed, 114 insertions(+) create mode 100644 pyrival/graphs/centriod_shallowest_decomposition.py diff --git a/pyrival/graphs/centriod_shallowest_decomposition.py b/pyrival/graphs/centriod_shallowest_decomposition.py new file mode 100644 index 00000000..6e48499f --- /dev/null +++ b/pyrival/graphs/centriod_shallowest_decomposition.py @@ -0,0 +1,114 @@ +""" + This file contains the implementations of centroid decomposition + and the shallowest decomposition of a tree, based on the blog + + https://codeforces.com/blog/entry/125018 + + The two implementations are meant to be interchangable. + In particular this allows easy comparision between the + methods. + + + # EXAMPLE OF HOW TO USE (https://codeforces.com/contest/321/problem/C) + + n = int(input()) + graph = [[] for _ in range(n)] + for _ in range(n - 1): + u,v = [int(x) - 1 for x in input().split()] + graph[u].append(v) + graph[v].append(u) + + decomposition_tree, root = centroid_decomposition_tree(graph) + #decomposition_tree, root = shallowest_decomposition_tree(graph) + + labels = [0] * n + bfs = [root] + for node in bfs: + for child in decomposition_tree[node]: + labels[child] = labels[node] + 1 + bfs.append(child) + + alphabet = [chr(ord('A') + i) for i in range(26)] + print(' '.join(alphabet[label] for label in labels)) +""" + + +""" + Standard implementation of centroid decomposition using rerooting. + Takes O(n log n) time. +""" + +def centroid_decomposition_tree(graph): + n = len(graph) + graph = [c[:] for c in graph] # copy + bfs = [0] + for node in bfs: + bfs += graph[node] + for nei in graph[node]: + graph[nei].remove(node) + size = [0] * n + for node in reversed(bfs): + size[node] = 1 + sum(size[child] for child in graph[node]) + decomposition_tree = [[] for _ in range(n)] + def centroid_reroot(u): + N = size[u] + while True: + for v in graph[u]: + if size[v] > N // 2: + size[u] = N - size[v] + graph[u].remove(v) + graph[v].append(u) + u = v + break + else: # u is the centroid + decomposition_tree[u] = [centroid_reroot(v) for v in graph[u]] + return u + decomposition_root = centroid_reroot(0) + return decomposition_tree, decomposition_root + +""" + Implementation of the shallowest tree decomposition based on https://codeforces.com/blog/entry/125018 + Takes O(n) time. + This is similar to centroid decomposition except that + it sometimes is ''shallower'' than the centroid decomposition. +""" + +def ctz(x): # Count trailing zeroes + return (x & -x).bit_length() - 1 + +def shallowest_decomposition_tree(graph, root=0): + n = len(graph) + forbidden = [0] * n + decomposition_tree = [[] for _ in range(n)] + stacks = [[] for _ in range(n.bit_length())] + def extract_chain(labels, u): + while labels: + label = labels.bit_length() - 1 + labels ^= 2**label + v = stacks[label].pop() + decomposition_tree[u].append(v) + u = v + dfs = [root] + while dfs: + u = dfs.pop() + if u >= 0: + forbidden[u] = -1 + dfs.append(~u) + for v in graph[u]: + if not forbidden[v]: + dfs.append(v) + else: + u = ~u + forbidden_once = forbidden_twice = 0 + for v in graph[u]: + forbidden_twice |= forbidden_once & (forbidden[v] + 1) + forbidden_once |= forbidden[v] + 1 + forbidden[u] = forbidden_once | (2**forbidden_twice.bit_length() - 1) + label_u = ctz(forbidden[u] + 1) + stacks[label_u].append(u) + for v in graph[u]: + extract_chain((forbidden[v] + 1) & (2**label_u - 1), u) + max_label = (forbidden[root] + 1).bit_length() - 1 + decomposition_root = stacks[max_label].pop() + extract_chain((forbidden[root] + 1) & (2**max_label - 1), decomposition_root) + return decomposition_tree, decomposition_root