解法メモ AtCoder Grand Contest 045 F - Division into Multiples

問題文：[AtCoder Grand Contest 045 F - Division into Multiples](https://atcoder.jp/contests/agc045/tasks/agc045_f)

# よいグループになるA,Bの個数を求める

まず、よいグループになるときの$A$の個数$x$、$B$の個数$y$の組$(x,y)$を網羅することを考える。

### 求めるべき(x,y)の要件

以下の式を満たす組$(x,y)$を全て求めたい。

```math
xA + yB \equiv 0 \pmod C \ \ \ \ \ \ \dots式(1)
```

ただし、最終目的がグループ数最大化なので、他の組より$x$も$y$も大きいような組は不要。つまり、$x$を昇順に並べると$y$が降順に並ぶように網羅する。

### 準備

まず、$A,B,C$の3つに共通の最大公約数で、$A,B,C$を割っておく。そうしても、$(x,y)$の組は変わらない。
更に、自分は気づかなかったが、$A,C$の最大公約数$g$で、$A$と$C$と$Y$を割っておくこともできる（$Y$は割り切れなくても切り捨ててよい）。なぜなら、$B$と$g$は互いに素であるため（$B$は既に$A,B,C$の共通の最大公約数で割ってあるので）、式(1)より、$y$が$g$で割り切れるはずである。そのため、$A$と$C$を$g$で割ってから求めた組$(x,y)$は、$g$で割らずに求めた組$(x,y)$に比べて$y$が$1/g$倍になっている。よって、$Y$を$g$で割っておけば、作れるよいグループ数が同じになる。
$g$で割らなくても解くことはできるが、説明が簡単になるので以下割ってあるものとする。同様に、$B,C$の最大公約数$g'$で$B$と$C$と$X$を割っておく。以上により、$A$と$C$は互いに素、$B$と$C$は互いに素になる。


### 基本アルゴリズム

組$(x,y)$には、以下の2つの性質がある。

<dl>
<dt>性質1</dt>
<dd>
組$(x,y)$が1つ求まったとする。すると、$x$と$y$を共に2倍、3倍、…してできる組$(kx,ky)$ $(k\ge 2)$も式(1)を満たす。但しこのままでは元の組$(x,y)$より大きいので不要な組。
</dd>
<dt>性質2</dt>
<dd>
組$(x,y)$の$y$が$C$以上なら、$C$を引いたもの$(x,y-C)$も条件を満たす。$x$についても同じ。
</dd>
</dl>

この2つの性質を利用し、次のようにして、$x$昇順＆$y$降順な網羅をするアルゴリズムを考えることができる：

まず、$(x=0,y=C)$は解。次に、$x=1$のときの$y$を求める。$\pmod C$での割り算を使えば簡単に求まる。

```math
\begin{align}
A+yB&\equiv 0 &\pmod C \\
y   &\equiv -A/B &\pmod C
\end{align}
```

この$(x=1,y)$を種にして、$2$倍～$C$倍まで順に試すことを考える。$y$が$C$以上になったら常に$C$を引くものとする。すると、$y$が今までのどの$y$よりも小さい場合だけ採用していけば、$x$が昇順、$y$が降順に並ぶように組$(x,y)$を網羅できる[^a]。

### 高速化

上記を愚直に実行すると計算量が$O(C)$になり、間に合わない。うまくスキップする方法を考える。

$x$昇順＆$y$降順に並んだ組$(x,y)$を$(x_i,y_i)$とする。下図は、横方向に$y$の値を取り、$(x_0=0,y_0=C),$ $(x_1=1,y_1)$の$y_0$と$y_1$をプロットしたものである。また、$\pmod C$で同じになる値を同一視し、負の方にもプロットする。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/605081/95d6ab81-cb13-a8ba-8fcd-50da6ec781da.png)

ここに、$(x_1,y_1)$を$k$倍$(k=2,\dots,C)$したもの（$C$を超えたら$C$を引く）を順にプロットしていくことを考える。下図のように、$y_0-y_1$間の距離と同じ幅で、$2$倍、$3$倍、… がプロットされ、それがそのまま$(x_2,y_2),$ $(x_3,y_3),$ $\dots$になる。$0$をまたぐ直前まで同様に続く。$0$をまたぐ直前を$(x_p,y_p)$とする。$y_1$が$C/2$以下の場合は、$(x_p,y_p)$ $=(x_1,y_1)$ になる。$0$と$y_p$の間の距離を$d$ $(=y_p)$とする。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/605081/e9ba07a2-a5f0-5087-a433-8c4ff66dc64e.png)

更に$k$を$1$ずつ増やしていくと、今度は$y_1$から$d$離れた場所、$y_2$から$d$離れた場所、…、というように、順にプロットされていく。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/605081/b6a79a01-a2c3-b8ab-d957-a6b6975e1764.png)

これを繰り返し、次に$x$昇順＆$y$降順となる点が出現するのは、つまり次に$0$～$y_p$の間にプロットされるのは、$(x_1,y_1)$の負側の点から$d$ずつ刻んでいき、$0$を超えた瞬間である。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/605081/5a347b1e-ff0e-7547-285d-4142d77ffe77.png)

よって、刻む回数を$t$とすると、

```math
\begin{align}
t &= \lceil (y_0-y_1)/d \rceil \\
x_{p+1} &= t*x_p + x_1 \\
y_{p+1} &= t*y_p + (y_1-C)
\end{align}
```

$(x_{p+1},y_{p+1})$が決まる周辺を拡大したものが下図である。$d$進むと、$x$が$x_p$増え、$y$が$y_p$増える。よって、$d$進んで$(x_{p+1},y_{p+1})$に到達する際の手前の点は、$(x=x_{p+1}-x_p,$ $y=y_{p+1}-y_p)$である。そして、その2つの点が、この時点で正負それぞれの方向で$0$に最も近い2点である。$0$と$y_{p+1}$の間の距離を$d'$ $(=y_{p+1})$とする。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/605081/1dcc6182-46fa-3764-f68c-bb0047e026da.png)

この後、$k$を$1$ずつ増やしていくと、$x=1$のときの点から$d'$離れた場所、$x=2$のときの点から$d'$離れた場所、…、というように、既出の点から$d'$離れたところに必ずプロットされていく。よって、次に$x$昇順＆$y$降順となる点が出現するのは、つまり次に$0$～$y_{p+1}$の間にプロットされるのは、下図のように、$(x=x_{p+1}-x_p,$ $y=y_{p+1}-y_p)$から$d'$離れた場所にプロットされるとき[^b]。よって、

```math
x_{p+2} = x_{p+1}-x_p + x_{p+1} = 2x_{p+1}-x_p \\
y_{p+2} = y_{p+1}-y_p + y_{p+1} = 2y_{p+1}-y_p
```

これは、$(x_p,y_p)$と$(x_{p+1},y_{p+1})$の間の距離$d''$で、等間隔に刻んでいくと見なすこともできる。これが$0$をまたぐ直前まで同様に続く。$0$をまたぐ直前を$(x_q,y_q)$とする。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/605081/2f4b7193-03f5-452c-a54a-e02e6241c41c.png)

同様の議論を繰り返すことで、$(x_{q+1},y_{q+1})$は、下図のとおり$d'''$を取ると、$(x_{p+1}-x_p,$ $y_{p+1}-y_p)$から$d'''$ずつ刻んで$0$を超えた瞬間の点なので、以下の計算で求まることが分かる。

```math
\begin{align}
d''' &= y_q \\
t &= \lceil (y_p-y_{p+1})/d''' \rceil \\
x_{q+1} &= t*x_q + (x_{p+1}-x_p) \\
y_{q+1} &= t*y_q + (y_{p+1}-y_p)
\end{align}
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/605081/8c685d44-694e-a5c3-ca8c-a0cf2933a5b8.png)

以上を再帰的に繰り返し、$(x=C,y=0)$にたどり着いたら終了である。ただし、後述するとおり、途中で最適解を決定できるので、最後まで計算する必要はない。

また、$(x_p,y_p)$から$(x_q,y_q)$まで$d''$ずつ刻み続ける処理は、最大で$10^9$回程度繰り返す可能性があるので、実際には$(x_p,y_p)$と$(x_{p+1},y_{p+1})$と刻む回数$s$だけを持っておく。

# よいグループが何個選べるか求める

### 問題設定

次に、$x$の合計が$X$以内、$y$の合計が$Y$以内になる制約の中で、求めた組$(x_i,y_i)$から重複を許して最大何個選べるかを考える。下図は、横軸$x$、縦軸$y$の二次元平面に組$(x_i,y_i)$をプロットしたものである。前述した、等間隔に並ぶ$(x_p,y_p),$ $(x_{p+1},y_{p+1}),$ $\dots ,$ $(x_q,y_q)$が、図でも折れ線の1つの線分上に等間隔に並ぶ。（図には折れ線を3つしか表示できないが、実際には$x$軸/$y$軸に限りなく近づきながら折れ線が多数続くこともある。）

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/605081/7e577b33-7af3-c1ac-c3d7-9e3c0e2541aa.png)

原点$O$から、プロットした各点までの位置ベクトルを考える。これらの中から好きなベクトルを好きな個数選んで加算していき、原点と点$(X,Y)$で囲まれる長方形からはみ出さないという条件のもとで、最大何個加算できるか、という問題になる。

何となく、$(X,Y)$方向の両脇の2つのベクトル$U,V$だけを使えば良さそうに見える。そのつもりで考えると、実際にそうであることを証明できる。

### 「両脇」のみで良いことの証明

原点と$(X,Y)$を通る直線より左上の領域を$G$、右下の領域を$H$とする。加算したベクトルの集合$S$が個数最大の解であるとする。領域$G$内で$U$以外のベクトルが$S$に含まれていたら、$S$に含まれ領域$H$にあるベクトルを1つ選び、両ベクトルを近づけるように1つずつ動かしても、やはり条件を満たしたままであり（ベクトルの合計の$x$座標も$y$座標も同じか小さくなるため）、最適解である。これを繰り返せば、領域$G$内では$U$しか含まない最適解を構成できる。もし繰り返している途中で、領域$H$内のベクトルが$S$に1つもなくなったら、それはつまり$S$が全て領域$G$内のベクトルであるということであり、全ベクトルを$U$とするのが最適解（の1つ）である。よって、いずれにせよ領域$G$内では$U$のみ使うとしてよい。同様にして、領域$H$内では$V$のみ使うとしてよい。

### 個数の計算

最後に、ベクトル$U$と$V$を何個ずつ足すのが最適か求める。原点からベクトル$U$を加える個数分だけ伸ばし、$(X,Y)$からベクトル$V$を加える個数分だけマイナス方向に伸ばしたときに、ベクトル$U$側が「左下」、ベクトル$V$側が「右上」になっていればよい。その条件を満たした上で、$U$と$V$の個数の和を最大にしたい。

$U=(x_U,y_U),$ $V=(x_V,y_V)$とする。下図のように、原点からベクトル$U$方向に伸びる直線と、$(X,Y)$からベクトル$-V$方向に伸びる直線の交点を$W$とする。すると、最適解の1つは、$W$をまたぐ位置の両脇、すなわち、ベクトル$U$の個数が下図における$u$個か$u+1$個のいずれかになる。なぜなら、例えばベクトル$U$が$u-1$個のときを考えてみると、$u$個のときに比べ$x$方向に$x_U$後退する。しかし、$x_V$は$x_U$より大きいので、この後退により$U$と$V$の個数の和が増加することはあり得ない。同様に、$u+2$個のときは、$y_V<y_U$であることを用いて増加があり得ないことが示せる。よって、ベクトル$U$が$u$個のときと$u+1$個のときの2通りを試すことで答が求まる。$u$の値は、二分探索をする、交点$W$を計算する、などの方法で求めることができる。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/605081/14929d55-b009-f804-30eb-797f247823f6.png)


# 感想

問題文はシンプルで取っつきやすいのに、何段階もの考察が必要で、奥の深いすごい問題だと思いました。相当な日数を費やしましたが、解けただけで大満足です。


[^a]: 準備で$A,C$や$B,C$の最大公約数で割っていない場合、このあたりを適切に調整する。$x$は$g'$の倍数にしかなり得ないので、$x=g'$から始める、など。
[^b]: 但し、その場所が負のときは、後述の$(x_q,y_q)$を、$(x_q,y_q)=(x_{p+1},y_{p+1})$として次の説明に進む。