Max Weighted Floor of Linear 解説
解説: Max Weighted Floor of Linear
yukicoder で開催されている競技プログラミングコンテスト Advent Calendar Contest 2025 の Day4 (2025-12-04) の問題、 Max Weighted Floor of Linear の解説です。
問題文

各テストケースについて、 正の整数
つまり、整数
制約
1 \leq T \leq 10^5 1 \leq N,M \leq 10^9 -10^9 \leq A,B \leq 10^9 0 \leq C,D \lt M - 入力される値はすべて整数
解説
- (「正規化」の項目で説明)
となるよう正規化し、 0 \leq C,D \lt M が綺麗な階段関数 ( \lfloor(C x+D)/M\rfloor の時に x=0 から始まり、 0 が増えるごとに高々 x しか増えない階段関数 ) になるように保ちます 1 とおきます y=\lfloor(C x+D)/M\rfloor,\ \mathtt{yMax}=\lfloor(C(N-1)+D)/M\rfloor - (「区間端点」の項目で説明) 各
を取る区間において最大値を取り得る y\in 0,1,\dots,\mathtt{yMax}\rbrace を、変数 x を用いて立式します。 y
の不等式を解くと、 y\leq (C x+D)/M\lt y+1 ごとの区間の式を求める事ができます。 y の時、式はただの一次関数 \mathtt{yMax}=0 になるので、両端の値の最大値 Ax が答え。 \max(0, A(N-1)) の値を固定した時、 y なら 区間の左端 A\leq 0 が最適。ただし、 x=\ell(y) の時は y=0 が最適な事に注意。 x=0 の値を固定した時、 y なら 区間の右端 A\geq 0 が最適。ただし、 x=r(y) の時は y=\mathtt{yMax} が最適な事に注意。 x=N-1 - (「小問題への再帰」の項目で説明)
の式に、それぞれ最適となる式 Ax + By または x=\ell(y) を代入すると、元の形の式が現れるので再帰的に処理します。 x=r(y)
なら、 A \geq 0 の場合を分けて、 y=\mathtt{yMax}\ (x=N-1) の区間を再帰的な処理に回した結果の値と、 0\leq y\lt \mathtt{yMax} の場合の値の、最大値が答えです。 y=\mathtt{yMax}\ (x=N-1) なら、 A \lt 0 の場合を分けて、 y=0\ (x=0) のように変数変換を行い、 y=t+1 の区間を再帰的な処理に回した結果の値と、 0\leq t\lt \mathtt{yMax} の場合の値の、最大値が答えです。 y=0\ (x=0)
と定義します。
この計算を、以下のような型の上で処理する、非再帰ループもしくは末尾再帰による実装方針を説明します。
-
: これまでに求めた端点の最大値\mathtt{maxAcc}_k -
: 定数項の累積和\mathtt{sumAcc}_k - 初期値
での値\mathtt{maxAcc}_0 = \mathtt{sumAcc}_0 = A L + B\lfloor (C L + D)/M \rfloor\quad : \quad x = L
- 初期値
-
: 現在の小問題のパラメータn_k,\ m_k,\ a_k,\ b_k,\ c_k,\ d_k - 初期値
n_0 = R - L,\ m_0 = M,\ a_0 = A,\ b_0 = B,\ c_0 = C,\ d_0 = (C L + D) \bmod M
- 初期値
この後は、以下のように進めて行きます。
-
を階段関数とみて、区間ごとに端点だけ見れば良い状況に簡約する。Y(x)=\lfloor (cx+d)/m \rfloor -
となるような区間y \leq Y(x) \lt y+1 の端点\ell(y) \leq x \leq r(y) の式を得る。\ell(y), r(y) -
がユークリッド互除法的になるような小問題へと再帰的に分解して、効率的に計算する。(m,c)
正規化
後に述べる「区間端点」「小問題への再帰」の議論と計算を簡単にするため、まずパラメータ
任意の整数
が成り立ち、最大化に無関係な定数項
\mathtt{sumAcc}' = \mathtt{sumAcc} + b \lfloor d / m \rfloor a' = a + b \lfloor c / m \rfloor c' = c \bmod m = c - m\lfloor c / m \rfloor \quad (0 \leq c' \lt m) d' = d \bmod m = d - m\lfloor d / m \rfloor \quad (0 \leq d' \lt m)
を定めると、
のように変換できます。
以降は、正規化された係数を前提にして考えます。
区間端点
をなします。
の区間が 0 \leq x \lt N に対して定まる区間 y \in \lbrace 0, 1, \dots, Y'\rbrace ごとに分割されて、 一次関数 I(y) が定義されているイメージです。 A' x + B y
また、
したがって
また、一次関数
-
のとき 区間a' \geq 0 上で 一次関数I(y) は非減少であり、x \mapsto a' x + b y で最大x = r(y) -
のとき 区間a' \leq 0 上で 一次関数I(y) は非増加であり、x \mapsto a' x + b y で最大x = \ell(y)
となります。
小問題への再帰
とすると、次の場合分けが成り立ちます。(場合分けの条件は必ずしも排反とはしていません。判定順は任意です。)
以下で、それぞれの場合分けについて証明を与えます。
a' \leq 0,\ b \leq 0 の場合
a' \geq 0,\ b \geq 0 の場合
\mathtt{yMax} = 0 の場合
このとき
\mathtt{yMax} \geq 1,\ a' \geq 0 の場合
\mathtt{yMax} \geq 1,\ a' \leq 0 の場合
非再帰での実装例
の関係が
実装例 (PyPy3):
# mwf(L,R,m,a,b,c,d) = max_{x in [L,R)} (a*x + b*floor((c*x + d)/m))
def mwf(L: int, R: int, m: int, a: int, b: int, c: int, d: int) -> int:
assert L < R and 0 < m
n = R - L
qd0, d = divmod(c * L + d, m)
max_acc = sum_acc = a * L + b * qd0
while True:
qc, c = divmod(c, m)
a += b * qc
qd, d = divmod(d, m)
sum_acc += b * qd
y_max = (c * (n - 1) + d) // m
max_acc = max(max_acc, sum_acc, sum_acc + a * (n - 1) + b * y_max)
if (a <= 0 and b <= 0) or (a >= 0 and b >= 0) or y_max == 0:
return max_acc
if a < 0:
sum_acc += a + b
n, m, a, b, c, d = y_max, c, b, a, m, (m - d - 1)
if __name__ == "__main__":
import sys
T = int(sys.stdin.readline())
for _ in range(T):
N, M, A, B, C, D = map(int, sys.stdin.readline().split())
print(mwf(0, N, M, A, B, C, D))
テストケースの計算過程
maxAcc sumAcc l r m a b c d
#0 正規化前 : max( 0, 0 + mwf(0,1000000000, 102334155, 433494437,-701408733, 63245986, 31415926))
#0 正規化後 : max( 92488359, 0 + mwf(0,1000000000, 102334155, 433494437,-701408733, 63245986, 31415926))
#1 正規化前 : max( 92488359, 0 + mwf(0, 618033988, 63245986,-701408733, 433494437, 102334155, 70918228))
#1 正規化後 : max( 433494437, 433494437 + mwf(0, 618033988, 63245986,-267914296, 433494437, 39088169, 7672242))
#2 正規化前 : max( 433494437, 599074578 + mwf(0, 381966010, 39088169, 433494437,-267914296, 63245986, 55573743))
#2 正規化後 : max( 433494437, 331160282 + mwf(0, 381966010, 39088169, 165580141,-267914296, 24157817, 16485574))
#3 正規化前 : max( 433494437, 331160282 + mwf(0, 236067976, 24157817,-267914296, 165580141, 39088169, 22602594))
#3 正規化後 : max( 462736810, 331160282 + mwf(0, 236067976, 24157817,-102334155, 165580141, 14930352, 22602594))
#4 正規化前 : max( 462736810, 394406268 + mwf(0, 145898033, 14930352, 165580141,-102334155, 24157817, 1555222))
#4 正規化後 : max( 462736810, 394406268 + mwf(0, 145898033, 14930352, 63245986,-102334155, 9227465, 1555222))
#5 正規化前 : max( 462736810, 394406268 + mwf(0, 90169942, 9227465,-102334155, 63245986, 14930352, 13375129))
#5 正規化後 : max( 462736810, 457652254 + mwf(0, 90169942, 9227465, -39088169, 63245986, 5702887, 4147664))
#6 正規化前 : max( 462736810, 481810071 + mwf(0, 55728088, 5702887, 63245986, -39088169, 9227465, 5079800))
#6 正規化後 : max( 481810071, 481810071 + mwf(0, 55728088, 5702887, 24157817, -39088169, 3524578, 5079800))
#7 正規化前 : max( 481810071, 481810071 + mwf(0, 34441852, 3524578, -39088169, 24157817, 5702887, 623086))
#7 正規化後 : max( 481810071, 481810071 + mwf(0, 34441852, 3524578, -14930352, 24157817, 2178309, 623086))
#8 正規化前 : max( 481810071, 491037536 + mwf(0, 21286234, 2178309, 24157817, -14930352, 3524578, 2901491))
#8 正規化後 : max( 483370049, 476107184 + mwf(0, 21286234, 2178309, 9227465, -14930352, 1346269, 723182))
#9 正規化前 : max( 483370049, 476107184 + mwf(0, 13155615, 1346269, -14930352, 9227465, 2178309, 1455126))
#9 正規化後 : max( 485334649, 485334649 + mwf(0, 13155615, 1346269, -5702887, 9227465, 832040, 108857))
#10 正規化前 : max( 485334649, 488859227 + mwf(0, 8130616, 832040, 9227465, -5702887, 1346269, 1237411))
#10 正規化後 : max( 485548358, 483156340 + mwf(0, 8130616, 832040, 3524578, -5702887, 514229, 405371))
#11 正規化前 : max( 485548358, 483156340 + mwf(0, 5024996, 514229, -5702887, 3524578, 832040, 426668))
#11 正規化後 : max( 485548358, 483156340 + mwf(0, 5024996, 514229, -2178309, 3524578, 317811, 426668))
#12 正規化前 : max( 485548358, 484502609 + mwf(0, 3105618, 317811, 3524578, -2178309, 514229, 87560))
#12 正規化後 : max( 485548358, 484502609 + mwf(0, 3105618, 317811, 1346269, -2178309, 196418, 87560))
#13 正規化前 : max( 485548358, 484502609 + mwf(0, 1919377, 196418, -2178309, 1346269, 317811, 230250))
#13 正規化後 : max( 485848878, 485848878 + mwf(0, 1919377, 196418, -832040, 1346269, 121393, 33832))
#14 正規化前 : max( 485848878, 486363107 + mwf(0, 1186239, 121393, 1346269, -832040, 196418, 162585))
#14 正規化後 : max( 485866169, 485531067 + mwf(0, 1186239, 121393, 514229, -832040, 75025, 41192))
#15 正規化前 : max( 485866169, 485531067 + mwf(0, 733135, 75025, -832040, 514229, 121393, 80200))
#15 正規化後 : max( 486045296, 486045296 + mwf(0, 733135, 75025, -317811, 514229, 46368, 5175))
#16 正規化前 : max( 486045296, 486241714 + mwf(0, 453101, 46368, 514229, -317811, 75025, 69849))
#16 正規化後 : max( 486045296, 485923903 + mwf(0, 453101, 46368, 196418, -317811, 28657, 23481))
#17 正規化前 : max( 486045296, 485923903 + mwf(0, 280031, 28657, -317811, 196418, 46368, 22886))
#17 正規化後 : max( 486045296, 485923903 + mwf(0, 280031, 28657, -121393, 196418, 17711, 22886))
#18 正規化前 : max( 486045296, 485998928 + mwf(0, 173068, 17711, 196418, -121393, 28657, 5770))
#18 正規化後 : max( 486045296, 485998928 + mwf(0, 173068, 17711, 75025, -121393, 10946, 5770))
#19 正規化前 : max( 486045296, 485998928 + mwf(0, 106961, 10946, -121393, 75025, 17711, 11940))
#19 正規化後 : max( 486080298, 486073953 + mwf(0, 106961, 10946, -46368, 75025, 6765, 994))
#20 正規化前 : max( 486080298, 486102610 + mwf(0, 66105, 6765, 75025, -46368, 10946, 9951))
#20 正規化後 : max( 486080298, 486056242 + mwf(0, 66105, 6765, 28657, -46368, 4181, 3186))
#21 正規化前 : max( 486080298, 486056242 + mwf(0, 40854, 4181, -46368, 28657, 6765, 3578))
#21 正規化後 : max( 486080298, 486056242 + mwf(0, 40854, 4181, -17711, 28657, 2584, 3578))
#22 正規化前 : max( 486080298, 486067188 + mwf(0, 25249, 2584, 28657, -17711, 4181, 602))
#22 正規化後 : max( 486080298, 486067188 + mwf(0, 25249, 2584, 10946, -17711, 1597, 602))
#23 正規化前 : max( 486080298, 486067188 + mwf(0, 15604, 1597, -17711, 10946, 2584, 1981))
#23 正規化後 : max( 486080298, 486078134 + mwf(0, 15604, 1597, -6765, 10946, 987, 384))
#24 正規化前 : max( 486080298, 486082315 + mwf(0, 9643, 987, 10946, -6765, 1597, 1212))
#24 正規化後 : max( 486080298, 486075550 + mwf(0, 9643, 987, 4181, -6765, 610, 225))
#25 正規化前 : max( 486080298, 486075550 + mwf(0, 5959, 610, -6765, 4181, 987, 761))
#25 正規化後 : max( 486080298, 486079731 + mwf(0, 5959, 610, -2584, 4181, 377, 151))
#26 正規化前 : max( 486080298, 486081328 + mwf(0, 3682, 377, 4181, -2584, 610, 458))
#26 正規化後 : max( 486080298, 486078744 + mwf(0, 3682, 377, 1597, -2584, 233, 81))
#27 正規化前 : max( 486080298, 486078744 + mwf(0, 2275, 233, -2584, 1597, 377, 295))
#27 正規化後 : max( 486080341, 486080341 + mwf(0, 2275, 233, -987, 1597, 144, 62))
#28 正規化前 : max( 486080341, 486080951 + mwf(0, 1405, 144, 1597, -987, 233, 170))
#28 正規化後 : max( 486080675, 486079964 + mwf(0, 1405, 144, 610, -987, 89, 26))
#29 正規化前 : max( 486080675, 486079964 + mwf(0, 867, 89, -987, 610, 144, 117))
#29 正規化後 : max( 486080675, 486080574 + mwf(0, 867, 89, -377, 610, 55, 28))
#30 正規化前 : max( 486080675, 486080807 + mwf(0, 535, 55, 610, -377, 89, 60))
#30 正規化後 : max( 486080675, 486080430 + mwf(0, 535, 55, 233, -377, 34, 5))
#31 正規化前 : max( 486080675, 486080430 + mwf(0, 330, 34, -377, 233, 55, 49))
#31 正規化後 : max( 486080675, 486080663 + mwf(0, 330, 34, -144, 233, 21, 15))
#32 正規化前 : max( 486080675, 486080752 + mwf(0, 203, 21, 233, -144, 34, 18))
#32 正規化後 : max( 486080752, 486080752 + mwf(0, 203, 21, 89, -144, 13, 18))
#33 正規化前 : max( 486080752, 486080752 + mwf(0, 125, 13, -144, 89, 21, 2))
#33 正規化後 : max( 486080752, 486080752 + mwf(0, 125, 13, -55, 89, 8, 2))
#34 正規化前 : max( 486080752, 486080786 + mwf(0, 76, 8, 89, -55, 13, 10))
#34 正規化後 : max( 486080752, 486080731 + mwf(0, 76, 8, 34, -55, 5, 2))
#35 正規化前 : max( 486080752, 486080731 + mwf(0, 47, 5, -55, 34, 8, 5))
#35 正規化後 : max( 486080765, 486080765 + mwf(0, 47, 5, -21, 34, 3, 0))
#36 正規化前 : max( 486080765, 486080778 + mwf(0, 27, 3, 34, -21, 5, 4))
#36 正規化後 : max( 486080765, 486080757 + mwf(0, 27, 3, 13, -21, 2, 1))
#37 正規化前 : max( 486080765, 486080757 + mwf(0, 17, 2, -21, 13, 3, 1))
#37 正規化後 : max( 486080765, 486080757 + mwf(0, 17, 2, -8, 13, 1, 1))
#38 正規化前 : max( 486080765, 486080762 + mwf(0, 8, 1, 13, -8, 2, 0))
#38 正規化後 : max( 486080765, 486080762 + mwf(0, 8, 1, -3, -8, 0, 0))
#38 の正規化後の段階で、
-
かつa\lt 0 b\lt 0 - 非増加関数となるため、右端点の値のみが候補
-
の区間において\ell\leq x\lt r 0=\lfloor(cx+d)/m\rfloor - 床関数の中身が
であるため、両端点の値のみが候補0
- 床関数の中身が
の2条件を満たします。(どちらか一方だけでも終了条件を満たします)
両端点の値は正規化するごとに maxAcc に反映済みであり、これ以上の最大値が得られる見込みがないため、 maxAcc の値
計算量
正規化により常に
補足: ユークリッド互除法の計算量
ユークリッドの互除法では、与えられた整数
に対して次の操作を、 a \geq b \gt 0 となるまで繰り返します: (a \bmod b) = 0 (a,\ b)\mapsto (b,\ a \bmod b) ここで、
回の操作で得られる余りを 1 とおきます。このとき、余りの定義から常に c := a \bmod b が成り立ちます。 0 \leq c \lt b 次に、余り
が常に c = a \bmod b 未満であることを示します。 a / 2
- もし
なら: b \leq (a / 2)
- このとき
より c \lt b \leq (a / 2) が成り立ちます。 c \lt (a / 2) - もし
なら: b \gt (a / 2)
- このとき
なので 1 \leq (a / b) \lt 2 です。 \lfloor a / b \rfloor = 1 - 除法の等式
より (a \bmod b) = a - b \lfloor a / b \rfloor です。 c = a - b - したがって
が成立します。 c = a - b \lt a - (a / 2) = (a / 2) - 以上より、いずれの場合も
が成り立ちます。 0 \leq c \lt (a / 2) したがって:
- 互除法の1回の操作で、分母パラメータは常に減少します。
であるため。 0 \leq (a \bmod b) \lt b - 互除法の2回の操作で、分母パラメータは確実に半分以下になります。
であるため。 0 \leq (a \bmod b) \lt (a / 2) - よって互除法は分母が指数関数的に減少し、高々
回の操作で、余りが (2 \lfloor \log_{2} b \rfloor + 1) となり終了します。 0
- ラメの定理とフィボナッチ数列の性質により、互除法の反復回数のより正確な上限は、黄金比
を用いて \varphi = (1 + \sqrt{5}) / 2 \approx 1.618 回とも表せますが、詳細は省略します。 (\lfloor\log_{\varphi} b \rfloor+1) - 以上より、互除法の反復回数は
、もしくは緩めに \mathcal{O}(\log b) と言えます。 \mathcal{O}(\log a)
遅くとも、余り
各反復を(多倍長の四則演算の影響を無視して)
補足: Min of Mod of Linear との関連
例えば、 Min of Mod of Linear は本問題の解法を用いて解けます。
- Max Weighted Floor of Linear (この問題)
\mathtt{mwf}(L,R,M,A,B,C,D)=\max\left\lbrace Ax+B\left\lfloor\dfrac{Cx+D}{M}\right\rfloor\mathrel{\Bigg\vert}x\in\mathbb{Z},\ L\leq x\lt R\right\rbrace -
Min of Mod of Linear
\begin{aligned}\mathtt{minmod}(L,R,M,A,B)&=\min\left\lbrace (Ax+B)\bmod M \mid x\in\mathbb{Z},\ L\leq x\lt R\right\rbrace \newline &= \min\left\lbrace (Ax+B)-M\left\lfloor\dfrac{Ax+B}{M}\right\rfloor \mathrel{\Bigg\vert} x\in\mathbb{Z},\ L\leq x\lt R\right\rbrace \newline &= B-\max\left\lbrace -Ax+M\left\lfloor\dfrac{Ax+B}{M}\right\rfloor \mathrel{\Bigg\vert} x\in\mathbb{Z},\ L\leq x\lt R\right\rbrace \newline \mathtt{minmod}(L,R,M,A,B) &= B-\mathtt{mwf}(L,R,M,-A,M,A,B) \newline \end{aligned}
def min_of_mod_of_linear(L: int, R: int, m: int, a: int, b: int) -> int:
"""
min_{L <= x < R} ( (a*x + b) % m ) を計算して返す。
(a*x + b) % m
= a*x + b - m * floor((a*x + b)/m)
= b - ( -a*x + m*floor((a*x + b)/m) )
これは b - mwf(L, R, m, -a, m, a, b) を求める問題に帰着する。
前提: L < R, 0 < m
https://judge.yosupo.jp/problem/min_of_mod_of_linear
"""
assert L < R and 0 < m
return b - mwf(L, R, m, -a, m, a, b)
Discussion
出題時には、多くの提出者が取った解法は想定解とは若干異なっていました。
呼称はあまり定まっていないようですが、ざっくり言うと「モノイドの積を用いて、floor_sumの類題を解けるよう一般化した手法」のようでした。
このモノイドの積を用いた手法は、例えば以下に解説があるようです。
以下の提出例は、今回の問題に、更に arg max (最大値を取る任意のパラメータの値、今回は最大値を取る最小のx の値)を求めるモノイドを作ってみたものです。(この提出では、一旦 arg max を求めた後、それより小さい x において最大値が存在し得ない事を assert 内で再計算しているので若干実行は遅くなっています)
argmax まで求めるようにするにはコードの規模が大きくなってしまいましたが、モノイドの扱いに慣れていて、本番で良い性質のモノイドを構築できるようであれば、このモノイドを用いた手法を覚えておくのも良いかもしれません。以下の3つはモノイドの積を用いた解法の提出例です。
また、さらに別解として、 Min of Mod of Linear のソルバーを用いた提出もありました。こちらはまだあまり中身を精査していないのですが、このような事も出来るのですね…。