この記事は新歓ブログリレー2026 51日目の記事です。
ちなみに、 です。
このブログで使用する記号について
実数 に対して、 は、 以の最の整数を表すものとします。
また、実数 に対して、 は、 以の最の整数を表すものとします。
このブログの内容について
このブログでは以下の つの値を で求める方法を図を用いて紹介します。どちらもサンプルコードを用意しているので、もしコンテスト中の方がいればコピペして使えます。だたし、コピペする場合は赤字の部分に注意してください。
重要
以下のサンプルコードはusing ll = long long;及びこのfloor2関数を前提としています。利用する場合はこちらも忘れずにコピペしてください。
コードを展開する
/// @brief 有理数のfloorを求める
/// @param y
/// @param x
/// @return floor(y/x)
ll floor2(ll y, ll x){
if ((x^y) > 0){
x = abs(x);
y = abs(y);
return y/x;
}
else if ((x^y) < 0){
x = abs(x);
y = abs(y);
return -((y+x-1)/x);
}
else{
return y/x;
}
}
1と2のサンプルコードはこちら
1のサンプルコード
//floor_sum関数に4つの整数N,B,C,Dをこの順で渡してください。
//正常に動作することが保証される範囲は、N<=10^9, -10^9<=B,C,D<=10^9, B!=0です。
ll internal_floor_sum(ll A, ll B, ll C){
if (C < 0){return 0;}
if (A > B){return internal_floor_sum(B,A,C);}
if (B%A == 0){
return (1+floor2(C,A))*(1+floor2(C,B)) - (B/A)*floor2(C,B)*(floor2(C,B)+1)/2;
}
ll k = floor2(C-B*floor2(C,B),A);
return (1+k)*(1+floor2(C,B)) + floor2(B,A)*floor2(C,B)*(floor2(C,B)+1)/2 + internal_floor_sum(A, B%A, C-A*(floor2(B,A)*floor2(C,B)+k+1));
}
/// @brief `\sum_{i=0}^{N} \lfloor\frac{Ci+D}{B}\rfloor`を求める。
/// @param N
/// @param M
/// @param A
/// @param B
/// @return
ll floor_sum(ll N, ll B, ll C, ll D){
if (N < 0){
return 0;
}
if (B < 0){//Bを負にする。
B *= -1;
C *= -1;
D *= -1;
}
if (C > 0){//Cを負にするが、Cを-Cに置き換えてC>0として扱う。
D += N*C;
}
else{
C *= -1;
}
if (C == 0){
return (N+1)*floor2(D,B);
}
ll k = floor2(D-C*N,B);
return (N+1)*k + internal_floor_sum(B,C,D-B*(k+1));
}
2のサンプルコード
//floor_max関数に7つの整数N,A,B,C,D,E,Fをこの順で渡してください。
//正常に動作することが保証される範囲は、0<=N, -10^9<=A,B,C,D,E,F<=10^9, B!=0です。
ll internal_floor_max(ll A, ll B, ll C, ll D, ll E, ll F){
if (D < 0){return -1000000000000000000;}
if (C <= 0){return -1000000000000000000;}
if (E > 0){
if (B > C){swap(A,E);swap(B,C);}
ll M = floor2(D-C*floor2(D, C), B);
ll tempans = max(A*M+E*floor2(D, C), A*floor2(D, B)) + F;
return max(tempans, A*(1+M)+internal_floor_max(A, B, C%B, D-B*(1+M+floor2(C, B)*floor2(D, C)), E-A*floor2(C, B), F+A*floor2(C, B)*floor2(D, C)));
}
else return A*floor2(D, B) + F;
}
/// @brief `0<=x<=N`の下で、`A*floor2(C*x+D, B)+E*x+F`の最大値を求める。もし何かがおかしいなら`-10^18`が返される。
/// @param N
/// @param A
/// @param B
/// @param C
/// @param D
/// @param E
/// @param F
/// @return `max`
ll floor_max(ll N, ll A, ll B, ll C, ll D, ll E, ll F){
if (N < 0){
return -1000000000000000000;
}
assert(B != 0);
//マイナスを処理
if (B < 0){
B *= -1;
C *= -1;
D *= -1;
}
if (A < 0){
A *= -1;
C *= -1;
D *= -1;
D += B-1;
}
//自明なケース
if (C == 0 or A == 0){
return A*floor2(D, B) + F + max(0LL, E*N);
}
if (E == 0){
return A*floor2(max(0LL, C*N)+D, B) + F;
}
//Cの係数を調整
if (C > 0){
F += E*N;
E *= -1;
D += C*N;
}
else{
//`A*floor2(D-C*x, B)+E*x+F`, `A,B,C>0`にする
C *= -1;
}
//自明なケースを処理
if (E < 0){
return A*floor2(D, B) + F;
}
ll x_offset = floor2(D-C*N, B)+1;
D -= B*x_offset;
return A*x_offset + max(internal_floor_max(A,B,C,D,E,F), -A+E*N+F);
}
1について
という形はfloor sumと呼ばれていて、知っている人も多いと思います。しかし、これを で計算するとなると、その過程で数式を色々いじる必要があり、何が起きているかが分かりにくいです。そのため、平面上のある領域内の格子点を数える問題に帰着させ、それを図を用いて説明することにします。最後に動画があるので、それだけを見ても何となく理解できると思います。
まず、シグマの形は扱いづらいので、これを変形します。多少は数式をいじりますが、直感的な説明も載せておきます。
直感的な説明
もし、途中でシグマの中身が負になる場合は、十分大きい定数をシグマの中に足しておき、最後に引くことにすれば問題ないです。
このとき、この総和は、xy平面上の台形領域の内部とその境界上の格子点の個数を表します。
台形を長方形領域と三角形領域に分け、長方形領域内(境界を含む)の格子点を掛け算で数えることにすれば、三角形領域内(境界を含む)の格子点を数える問題に帰着させることができます。
数式をいじる過程をスキップしたい場合は次の章に進んでください。
まず、 が負のときは分母と分子に を掛けます。
次に、 が正のときは ではなく、 のように、逆から足します。( を に置き換えます。)すると、 は 以下になります。
また、 のときはシグマの中身が定数となり、簡単に計算できるので、 としても問題ないことが分かります。
以上より、 を仮定できます。 が負であると扱いづらいので、以下では を に置き換え、 とします。
この時点で、対象の式は と変形されることに注意してください。
次に、定数 を用いて以下のように変形します。
このとき、シグマの部分は平面上の領域 の内部と境界上に存在する格子点の個数を表します。
証明
領域 を とする。
次に、 座標を固定して の内部及び境界上の格子点を数え上げる。
であるような 内の格子点の個数は となる。 のとき、この式の値は であり、単調減少性より、 のときもこの式の値は なので、 だけを考慮すれば十分である。
したがって、 の内部及び境界上に存在する格子点の個数は以下の式で与えられる。
領域の境界が だと扱いにくいので、x軸正方向に だけ平行移動し、 とします。
以上のことから、floor sumは、 つの定数 を用いた領域 の内部及び境界上の格子点を数える問題に帰着されることが示されました。
三角形領域内の格子点の数え上げ
三角形領域 内の格子点を のような総和公式を用いて一瞬で求めることはできません。そのため、求められる部分から順次切り取っていくようにします。
以下では、三角形領域の境界線である直線 のことを直線 とします。
具体的には以下の手順を、終了と言われるまで繰り返します。
- もし、 が負なら終了する。
- もし、 なら と を入れ替える。
- もし、 が の倍数なら、格子点の個数は となり、これは総和公式で一瞬で求まる。操作を終了する。
- そうでないなら、三角形領域 内(境界を含む)の格子点の個数を求め(ステップ3のようにすることで簡単に求まる)、これを答えに加算する。そのあと、 と変換して境界線 をせん断変形する。そのあと、長方形領域 内(境界を含む)の格子点の数を掛け算で求めて答えに加算する。最後に、 を 軸正方向に だけ平行移動する。
ステップ4全体としては、 はそれぞれ に置き換えられることになる。
これだけだとよく分からないので、図を出して説明していきます。
1について
もし、 が負の場合は領域が存在しないため、そのまま終了します。
2について
もし、三角形領域が縦長の場合、 と を入れ替えて横長にします。この操作は、直線 に関して対称移動を行うだけなので、領域内の格子点の数は変わりません。
3について
が の倍数のとき、 座標を固定して数え上げるとシグマの中身は等差数列になるので、総和公式で簡単に求めることができます。
4について
これが一番重要です。
簡単に数えられる中で最大の三角形をとります。つまり、以下の図で、「青線の傾きの逆数が整数になる範囲で、青線の傾きが赤線の傾きを超えない最大」であり、「青線の 切片が最大」であるものを選択します。
青線の傾きの逆数が整数でなければならない理由は、後述するシグマの中身を等差数列にするためです。

上の図は、 です。(これ以降もこの具体例で説明します) また、青線の 切片は最大の に固定し、青線の傾きは の 通りを示しています。赤線の傾きは なので、これを超えない最大の傾きは となります。
一般の場合
一般の に対しては、青線の 切片は となり、青線の傾きは「 を超えない最大の値であって、の形で表せるもの」になるため、これは となります。
次に、青線と 軸と 軸で囲まれた領域内(境界を含む)の格子点の個数を求めます。

図に書かれている通り、青線と 軸と 軸で囲まれた領域内(境界を含む)の格子点の個数は 個となります。この値は答えに加算しておきます。
一般の場合
一般の場合、青線は傾き 切片 なので、青線の方程式は となる。したがって、 で固定し、 の範囲で足し合わせることで、総和公式を用いて格子点の個数を計算することができる。
次に、青線がy軸に重なるように平面上にあるすべての図形をせん断変形します。
すなわち、 を に置き換えます。変形のイメージは以下の図で示します。

実際に変形を行うと、以下のような図になります。(点線がせん断変形前の状態)

また、先ほど数えなかった点が存在する範囲は以下の画像のように変化します。青の点線上の点はすでに数えていることに注意してください。(紫色が変形前, 赤色が変形後)
重要
せん断変形前の格子点と、せん断変形後の格子点は 対 で対応するため、紫の領域内(境界を含む)と赤の領域内(境界を含む)の格子点の数は等しくなります。
ちゃんとした証明
紫の領域と赤の領域の内部(境界を含む)の格子点の個数をそれぞれ 座標を で固定して数えることを考えると、どちらのシグマの中身も になるため、それぞれの領域内(境界を含む)の格子点の数は等しい。

次に、せん断変形後の赤い領域において数えるべき残りの点を、長方形領域内の点と三角形領域内の点に分割すると、以下の画像のようになります。

左側の方の長方形領域の内部及び境界上に存在する格子点の個数は 個です。
そして、(点線でない)赤い線を 軸正方向に だけ移動することで、右側の三角形領域の内部(境界を含む)の格子点の個数は、「(傾きが負の有理数の)直線と、 軸と、 軸に囲まれた領域の内部(境界を含む)の格子点の個数」と等しくなるので、これは最初と同じ状態になります。したがって、ステップ1からやり直すことで、再帰的にこの問題を解くことができます。
一般の場合
まず、せん断変形によって直線 は に変化する。
すると、左側の長方形領域は となり、この内部及び境界上に存在する格子点の個数は となる。なお、長方形領域が存在しない場合でもこの式は正しく を返す。この値は答えに加算しておく。
最後に、右側の三角形領域の境界を座標軸に合わせるために、せん断変形後の直線を 軸正方向に だけ平行移動すると、方程式は となる。
したがって、結果的に つの定数 はそれぞれ に置き換えられることになる。
変数を置き換えた後に、ステップ からやり直すことで再帰的にこの問題を解くことができる。
計算量解析
ステップ にある通り、手順を1からやり直すたびに つの定数 はそれぞれ に置き換えられることになります。 はかなり複雑に変化しますが、 と に着目すると、 はそのままで、 は に変化していることになります。そして、ステップ では となるように と を交換することになり、終了条件の つも「 が の倍数」となっているので、これは正整数 , に対してユークリッドの互除法を適用する過程と全く同じとなります。
したがって、時間計算量は となりますが、このパラメーター は本来のfloor sumでは であったので、floor sum自体の時間計算量は となります。
図形的なイメージとしては、直線の傾きを、急な状態と緩やかな状態の間で交互に変化させ、各ステップでは三角形の面積( 格子点の個数)を約半分以下にしていることになるので、時間計算量は対数時間になることが分かると思います。下に図を載せておきます。
最初、黒線があるとき、各ステップによって、黒→赤→青→緑→紫→橙のように直線が変化していきます。(ただし、実際には傾きが急な直線(赤, 緑, 橙)は、直線 に関して対称な位置に移動して扱います。)
この図は です。

動画による説明
これまでのステップ ~ステップ をまとめたアニメーション動画を 通り用意しました。図がよくわからなくても多分大体の雰囲気は分かると思います。この動画では、ステップ の青領域内(境界を含む)の格子点と、せん断変形後の図の左側の長方形領域内(境界を含む)の格子点をまとめてカウントしています。
実装について
基本形のfloor sumを格子点の数え上げ問題に帰着した後、ステップ ~ をそのまま再帰関数またはwhile文として実装すればいいです。実装例はこのブログの上部にあります。
2について
という形は、あまり有名ではないかもしれませんが、これもfloor sumと同じように で求めることができます。数式をいじる方法は難しいというか、わからなかったため、これも先ほどと同じように平面上の格子点に関する問題に帰着させて解きます。
floor sumと比べて多少数式が増えるかもしれません。
まず、 としても問題ないことを示します。
または または のときは、単調増加または単調減少となるため、両端を試し、大きい方を答えにすればいいです。
のときは、分母と分子に を掛ければ となります。
のときは、 となるため、 とすることで、 としても問題ないことが分かります。
のときは、 を に置き換えることで、 にできます。
のときは、上記の過程より、 は単調減少なので、全体としても単調減少になります。よって、 の時の値が答えになります。よって、 としても問題ないです。
以上より、 としても問題ないことが示せました。
そして、floor sumの時と同様に を に置き換え、 とします。
この時点で、対象の式は となっていることに注意してください。
ここで、次の言い換えを行います。
は、「平面上の格子点 が を満たすときの の取りうる値の最大値」となります。
実際に具体的に点 を図示してみると以下のようになります。以下の図は であり、赤線が直線 です。

最大化する対象の式は であるので、さらに言い換えると、この問題は、「点 が上記の画像の青い点上のみに存在するとき、 の最大値を求める問題」に帰着させることができます。
以下では、 と置き、 の最大値を求めていきます。
このままでは点の 座標が負になることがあり、扱いづらいため、最も 座標の大きい点の 座標がちょうど になるように 軸方向に平行移動します。上の図では 軸正方向に だけ平行移動することになります。これにより、赤線の方程式は となります。ただし、平行移動を行ったことで、答えとなる の最大値が だけ増加してしまうため、代わりに に を加算しておきます。
一般の場合
座標が最も大きい点は であるので、 として、直線を だけ 軸正方向に平行移動すればいいことになる。これにより、直線の方程式は になる。この操作によって、最大値は だけ減少してしまうため、代わりに に を加算する。
実際に平行移動を行うと、以下の画像のようになります。

線形計画法の解法として、「端の点を試す」という手法がありますが、今回もそれは有効です。まず、 座標が負の点を考える必要をなくすために、 座標が である場合を試します。
座標が であるような青い点は複数存在する可能性がありますが、 より、そのような点のうち、 座標が最も大きい点のみを試せば十分であることが分かります。したがって、試すべき点は上の図では となります。
一般の場合
一般の場合でも、 座標が である点のうち、 座標が最大の点の座標は と確定するので、これを に代入して得られる が の最大値候補のひとつとなります。
以上より、この問題は の範囲で考えられるようになりました。
帰着後の問題を高速に解く
上記の様々な議論により、考えるべき格子点はfloor sumの時と同様に三角形領域の内部及びその境界上に限定することができました。
そしてこの問題も、floor sumの時と同様に、境界線を繰り返しせん断変形することで高速に解くことができます。
もう一度今から解く必要のある問題を書いておきます。
制約...
点 が にのみ存在しうるとき、 の最大値を求めよ。
これを解くためにこのような再帰関数「 」を定義します。
指示がない限り、 のように番号順に実行されます。
を以下のように定義する。
- もし、 ならば、 を返し、関数を終了する。
- もし、 ならば、 を返し、関数を終了する。
- もし、 ならば、4.へ、そうでなければ6.へ
- もし、 ならば、 と を入れ替え、 と も入れ替える。
- 左端での値 と右端での値 と の中で最も大きい値を返し、関数を終了する。
- 右端での値 を返し、関数を終了する。
これだけだとよく分からない(特に5.)ので、図を用いて説明していきます。
1について
のとき、そもそも考慮すべき点が存在しないため、最大値は存在しません。そのため を返します。
2について
であるということは、直前の関数内では が の倍数であったことになります。このときは、存在しうる点 はすべてが一直線上に並ぶため、左端または右端での値が最大となります。したがって、 の時は、この最大値に影響しないように を返すようにします。
3,6について
まず、常に が保証されます。
証明
帰納法で行います。
まず、関数を初めて呼び出すときは、制約から が成り立ちます。
次に、ある段階でsolveが呼び出されたときに が成り立っていて、それまでもずっと であったと仮定します。
このとき、 であれば6.に行き、そのまま関数が終了するため、 は成り立ちます。
であり、 であれば、 と が入れ替わるだけなので問題ありません。
であり、 であれば、次に呼ばれる 関数には第一引数に がそのまま渡されるので、次の関数でも は成り立ちます。
のとき、 を最大化するためには をできるだけ大きく、 をできるだけ小さくしたいため、領域内の右端かつ下端である点 を試すのが最適です。そのため、6に行きます。
4について
もし領域が縦長 であるなら、 を入れ替え、 も忘れずに入れ替えて、領域を横長にします。
5について
先ほどの図をもう一度載せます。
両端の点は紫色のバツ印にしてあります。
右端の点は直感的な右端で問題ありませんが、左端は必ずしも 軸上であるとは限りません。より厳密には、右端の点は 座標が最小の の であり、左端の点は 座標が最大の の になります。

まずはこの両端の点についてを試します。左端での値は で、右端での値は であるので、この つの値が最大値候補になります。
そのあと、floor sumと同じように とせん断変形をし、 軸正方向に だけ平行移動します。平行移動後は、上図での左端の点は に移ります。以下にその図を載せておきます。(移動前の点と線は薄く表示しています。)

このせん断変形と平行移動により、直線 の方程式は になり、最大化対象の式である も になります。 これが複雑なせいで次に呼ばれる 関数の中身も複雑になっています。
移動後の図に着目すると、 座標が の点が複数ありますが、試す必要があるものは結局 座標が最大のものになります。しかし、その点は左端の点であったものであり、すでに候補にあがっているので試す必要はないです。以上より、 座標が負の点は考える必要がなくなりました。
したがって、最初と同じ状況が生じるため、今の状況に対して 関数を呼び、その返り値と端の値を比べてより大きい方を返すことで再帰的にこの問題を解くことができます。
なお、次に呼ぶべき関数の各引数 はそれぞれ になります。
計算量解析
関数の第 引数と第 引数に着目すると、floor sumのときと同様にして時間計算量が であることが分かります。
実装について
前処理で としたあと、ステップ ~ をそのまま再帰関数として実装すればいいです。実装例はこのブログの上部にあります。
最後に
紹介したアルゴリズムで殴れる問題をいくつか紹介しておきます。
ただし、floor sumは有名すぎるので、主に つ目になります。
https://judge.yosupo.jp/problem/min_of_mod_of_linear
であるので、上部で紹介したコードの中の floor_max 関数をコピペするだけで解けます。
https://atcoder.jp/contests/arc139/tasks/arc139_b
加算と 加算をそれぞれ 回, 回使うとすると、格子点限定の線形計画法の問題になり、これは つ目の説明中に出てきたものとほぼ同じようにして解けます。
ちなみに、この問題を解いたことでこのブログを書くことを決めました。
なお、C++で実装したところ、実質fastestを取ることができました。

明日の新歓ブログリレー2026の担当は @Water_Castle, @2251799813685248 です。