コンテンツにスキップ

競技プログラミング

(主に)F#による競技プログラミングの学習ログ

オンラインジャッジサイト

TODO 取り組みたい・F#版解説を作りたいオンライン教材

関連: 数値計算の学習ログ

技術メモ

学習メモ: 特に初学時

  • AtCoderなど解答・解説がある状況を前提にする.
  • まずは適当な時間を決めて自力で考える.
  • 自力で解けなければ解説を読み, 適当な時間を決めて実装だけ考える.
  • 他の人の解答を読む.

HaskellとProject Eulerとarithmoiパッケージ

HaskellでEulerやる人はarithmoiパッケージを入れておきましょう. 2009年ごろまでは一番解いてるHaskellerだったDaniel Fischerが書いたもので, クッソ速い整数冪根とかメビウス変換, ヤコビ記号などが手に入ります.

回数の計算

  • 再帰または無限リストで処理したくなるとき, それが回数に関わる計算ならmodや割り算を使うべし.

グラフ・ネットワーク

  • グラフ・ネットワークを追いかけるときは再帰またはscanlを使うとよい.
    • ただし無限ループがありうる. F#ならSeq.scanを使うか, 再帰なら辿った回数を記憶して適当なところで打ち切る. Seq.scanからのSeq.take nでTLEになったことがあり, F#ならinitInfinite的なyieldで処理した方がよさそう.
    • 参考

小数の処理

  • 状況に応じて高い精度を使う必要がある.
  • 有効桁など微妙な処理が出る場合は整数上で計算するのも一手

対象を捨てるか拾うか

  • 問題に応じて適切な方を選ぶべし.
  • 参考

文字列処理

  • 連続する文字の判定はスタックに積みつつ再帰を使うとよい.

両端の処理

  • ときどきリストや配列の端の処理の場合分けが必要.
  • 特に要素数が少ない場合, 両端に要素を追加してメイン処理のロジックを綺麗にするといいことがある.
  • 参考

TODO 累積和とscan

F#による関数型競技プログラミングへの道

参考記事・サイト

はじめに

  • created: 2022/11/28

意図・目的・背景

この記事群ではF#による競技プログラミング入門と題して関数型のデータ構造やアルゴリズムを議論します. 特にAtCoderを軸に展開します. AtCoderと関連した書籍・オンライン教材も適当な意味で組み合わせて使います. 2022/11時点では本格的な勉強をはじめて一年程度の競プロ初心者で, 私の学習ログの側面もあるため必ずしも専門的に優れた内容にはなっていません.

私は大学学部時代は物理, 修士は数学だったため計算機科学の素養がありません. 数値計算・数式処理系の素養があるわけでもありません. しかしこれらで遊び倒したいとは思っていていくらか勉強はしています. そしてその中で高速化に関連したデータ構造やアルゴリズムの重要性を痛感しています.

一方でプライベートで数学・物理・プログラミングのコンテンツ販売や通信講座を運営していて, そのコンテンツ作りの中で可視化やシミュレーションの需要に応えるための勉強も続けています. 自分が数学・物理を志す中高生だった頃に「こんなことをしたかった」と思っていた環境がいまや簡単に構築できます. そうした中高生向けのコンテンツ・サービス展開も含めて展開を考えているのも合わせ, 自分の趣味としての取り組みも含め, ゴリゴリの数値計算の厳しさとは別に何か遊べるネタがないか探す中で改めてデータ構造とアルゴリズム学習の観点から, 競技プログラミングに辿り着きました.

そして競技プログラミングは整数まわりの算数・数学要素もあれば, 場合の数や数え上げに関する数学要素もあります. とにかく大量に計算する意味で算数・数学枠に入れられるのではないかと思っていて, 実際に私はこの視点で楽しんでいます. 一般的なプログラミングで重要なコーナーケースの議論は競技プログラミングでも必要です. そして数学での反例の構成の訓練やその意義を知るためのアプローチの一つとしても使えないかと思って研究中です.

プログラミングの教育用の教材・サービスを作る上でもう一つ課題だったのはコードが腐る現象です. いわゆるアプリ開発・ゲーム開発では環境やツール・ライブラリの発展に応じて昔のコードが動かなくなります. このメンテナンスがとにかく苦行です. あくまで算数・数学・物理にこそ興味があって, プロダクトがほしいわけでもない状況でメンテナンスに時間を取られたくありません. 数値計算系のプログラムも同じで, 特に今流行りのPython・Juliaを使うとなるとライブラリを大量に使う以上メンテ地獄からは逃れられません. かといってC/C++/とgnuplotなどの枯れた技術を使うとそもそもの導入ハードルが高くなります. 一方データ構造とアルゴリズムは凝った言語機能を使うわけでもなく, 一度書いたらそう簡単には腐りません. 上で書いたように数学・物理への応用ではプログラムは高速化したいモチベーションが高いため, 決して無駄にもなりません. これも競技プログラミングで遊び倒してみようと決めた大きな理由の一つです.

2022/11時点で私はF#を仕事で使っているわけでもなければ, 現時点で関数型の言語を仕事で使っているわけでもありません. 特にプロジェクトベースの本格的なF#プログラムを書いた経験もなく, fsxでのスクリプトしか書いたことがありません. 最近はF#を書きたいから競技プログラミングに取り組んでいる側面さえあります. 自作Webアプリ作成で使っているJavaScriptをはじめPython・Juliaにはたくさん情報がある一方, お気に入りのF#は英語であっても極端に情報がありません. データ構造とアルゴリズムの観点から言えば関数プログラミングに特化した本やコンテンツ自体が少なく, あったとしてもHaskellがベースのように思います.

一方で.NET系としてC#には一定の人口がいます. 特にAtCoder社長のchokudaiさんはC#erと聞いています. そうした人を巻き込みつつF#人口を増やすべく, まずは自分から情報を出そうとしたのがこの記事群です.

言語選定の理由

まず関数プログラミングが一つの基準です. 仕事でHaskellを書いていた時期があり, 本当に大変だったものの非常に肌に合って読み書きしていて気持ちよかったのが理由です. 数値計算を考えるならC/C++/Rust, 数式処理を考えるならお手軽なSymPyがあるPythonがいいのでしょう. しかし便利に使える状況ならいわゆる関数型の言語を使いたいと思っています.

一方でHaskellは読み書きしていて楽しいものの私の手に余るのも現実です. 関数プログラミングの楽しさは享受しつつ, 程々にゆるい言語がないかと思ううちにF#に出会いました. Microsoft製の言語であるため, 私がメインマシンとして典型的に使うWindowsとの相性は問題ありません.

以前Windows上のOCamlではまっていたとき, OCamlのプロに「OCamlはWindowsで使うものではない」と言われたこともあり, OCamlはWindows対応がつらそうな印象があります. Haskellにも時々Linux前提のような印象を受けます.

Haskellほどではないにせよ, F#は標準ライブラリが充実しています. OCamlは「それもないのか」と思うほど標準ライブラリが充実していないように思います. 例えばArrayモジュールにsumがありません. もちろんその程度はすぐに書けますが, この程度の処理もいちいち書かないといけないのは億劫です. ライブラリを自由に導入できるわけではない競技プログラミング環境でOCamlはつらそうで, それもOCamlを避けた理由です.

自分だけならWSLなりDockerなり回避法はあります. しかし算数・数学・物理に取り組みたい中高生に真っ先に勧められる手段ではありません. 私のメインエディタはEmacsで, 私自身はあまり使っていないものの, 同じMicrosoft製のVSCodeによる軽量な開発環境整備も簡単でWindowsユーザーにも勧めやすい言語です. VSCode・Emacsともに程良い感じのREPLがあってとにかく快適です. (ライブコーディングの動画を撮るといいのかもしれません.)

Try F#のような環境もあるとはいえ, 情報の少なさもあって必ずしも中高生向けにF#を勧めにくい状況はありますが, 自分自身が気持ちよく取り組める言語として現状ではF#を選んでいます.

Haskellと違って雑なprintfデバッグもやりやすく, 型が強い割にはスクリプト言語のような書き心地で気持ちよく書けます.

AtCoder上でもいわゆる関数型言語はいくつかあります. 全ての言語と特徴が把握できていないものの, 少なくともClojureはあります. ただAtCoderのClojureは異様に遅いです. ちなみにAtCoder上の実行時間としてはF#は大して速くなく, 同種のコードでOCamlやHaskellの数倍から数十倍の時間がかかる場合はよくあります. ただし少なくともAtCoder上で他の言語と直接比較しても大したご利益はなく, コンパイル型の言語なら速度はほぼ問題ないというコメントもあります. そもそもF#を選んだ時点で一定程度最速を求めるのは放棄しているため, 他の言語との比較の意味での速度は必要以上に気にしません. ただし, もちろんF#プログラムとしての速度や効率はできる限り注意します.

F#のよい所

F#のよいところの宣伝といえばcannorinさんの記事が有名です.

他のよい点として, Haskellよりは遥かに命令型的に書きやすい点があります. C#を書いた経験がほぼないためひどくいい加減な感想ではあるものの, (おそらく)型まわりの指定がほぼないC#のように書けます. 特にCやPythonで書かれたアルゴリズムを直移植しやすい利点があります. 実際, 私自身も解答が思いつかずF#の解答提出がなかったり, 理解しにくいコードだった場合はPythonなど他の言語からコードを直移植してから書き直す場合があります. これらは人口が多いためサンプルコードも多く, 自分にとって読みやすいコードを見繕いやすくて便利です.

F#人口は少ないもののHaskellerは一定数いるため, Haskellから移植する場合もあります. さらに関数型で書くと逆にわかりにくくなる場合もあります. Haskellだとモナドの面倒なプログラムを書かないといけない場面でも, F#なら命令型的なコードが簡単に書けるのもよい所です.

AtCoderを選ぶ理由

F#が使えるからです. AOJ, Aizu Online JudgeはHaskell・OCamlはあってもF#がなく, アルゴ式もHaskellはありますがF#がありません. 私はデータ構造とアルゴリズムマニアというわけでもなく, そこまで調査に時間を使いたいわけでもないため, 手軽かつ有名で人口も多く日本語が使えて中高生にも勧めやすいとなるとAtCoderに利点があるように思います.

AOJにはアルゴリズムだけではなくデータ構造などに関しても教育用コンテンツ群があります. アルゴ式はもとから教育用サービスです. これらで使える言語ならこの二サービスもお勧めです.

書くコードの方針: 2022/11時点

曖昧に次の基準を採用して進めます.

  • 読みやすいコードにする
    • 学習用教材の側面もあるため当然
    • 書き捨てではなくあとで自分自身で参照する必要があるため自分も困る
  • なるべく短くする
    • 長いとそれだけで読むのが大変で疲れる
    • 一方でコードゴルフのような過度な短さは求めない
    • 変数名・関数名もなるべく読みやすくする
  • できるだけパフォーマンスに気をつける
    • ただし速くても読むのが大変な(現時点での私がすぐに分からない)コードにはしない
    • 現時点の自分がすぐに頭に入って応用して書けるコードにする
    • 自分のレベルが上がってきたら高速化はもっと気にする

解説の方針

  • 基本的なアルゴリズムの考え方・落とし込み方は公式の解説を前提にして深く解説しません. それをどうF#のプログラムに落とし込むかを考えます.
  • 場合によっては他の言語で書かれたプログラムをどうF#に落とし込むかも考えます.

お勧め学習法

何らかの手段でデータ構造やアルゴリズムの基本的な勉強はしているとして, ここでは実際の問題を解く場合のポイントを書きます. 一般的なプログラミング学習と同じく, 他人の書いたコードを読むのはとても勉強になります. 大事な点を箇条書きにします.

  • 自分が書きたい言語で, 多少長くても読みやすいプログラムを書く人をベンチマークする.
    • その人のステージや意識が変わらない限り, たいていの人は同じスタイルでプログラムを書いている.
    • コードゴルフ的に短く書きたい人はずっとそのスタイル.
    • インデントやスペースを使って読みやすく書く人はたいていずっとそのスタイル.
  • 速いコードを書く人をベンチマークする.
    • 競プロとしてコンテストで順位を上げる目的以外に, ある言語での最速を求める人もいる.
    • 言語によっては速く書くための工夫もあるため, 常に速いプログラムを書いている人は定点観測しよう.
    • Haskellではcojnaさんを見るといいだろう.
      • 最近は自作ライブラリを載せたコードを書いているため必ずしもアルゴリズムの基本構造がわかりやすいわけでもない.
      • ただし基本的には高速化を意識したコードを書いているのは間違いない.
  • F#では解答がない場合もよくあるため, その場合はOCaml, Haskell, Python, Cなどの解答を参照しよう.
    • F#はOCamlを参考にして作られたため, 構文レベルでよく似ていてコピペを少し調整すればそのまま動く場合がある.
    • Haskellは必ずしも読みやすくなく, ライブラリ利用も含めてHaskellでないと書きにくい(移植しにくい・多少の工夫が必要な)コードもある.
    • Python・Cは人口も多く, 短く読みやすいコードが比較的見つけやすい印象がある.
    • 他の言語は私の経験がない・AtCoder上できちんと見たことがないため, 論評できない.

競技プログラミングのためのF#入門

サンプルコード

ごちゃごちゃと説明を読むよりもコードをさっと見たい人も多いでしょう. データ構造とアルゴリズム, さらには私の競プロのコードを集めたAlgorithmsAndDataStructureByFSharpを参考にしてください. 特に次の2ファイルが役に立つでしょう.

  • Arithmetics.fsx
    • 競プロ用のmodつき階乗・順列・組み合わせの例を収録している.
    • その他n進展開とその復元などお役立ち関数も収録.
  • Reference.fsx
    • 基本は公式リファレンスの参照用まとめ.
    • サンプルが即実行できるように一緒に載せている.
    • 処理したい内容から日本語で検索しやすいように日本語も添えている.
    • Haskellとは関数名が違う場合にその情報を添えている.
    • Haskellにある便利関数がない場合はその実装を載せている.

基本的に収録しているF#コードのインデントは標準の4ではなく2です.

C#との相互運用

.NETの資産が使えます. F#との相性は必ずしもよくないものの, 入門レベルでも.NETの破壊的なQueueはよく使います. F#標準の非破壊的なQueueはないようです.

F#開発環境構築

  • インストール関連はメンテナンスが大変なため省略します. 公式情報を参照してください.
  • インストールが面倒ならTry F#があります.
  • Microsoft製なのもありVSCode+ionideがお勧めです.
    • 私はお気に入りエディタがEmacsだからEmacsで書いています. 特に勧めません.
  • 拡張子fsxでF#をスクリプト言語のように書いて使えます.
  • fsx+REPLを使うと気持ちよく書けます.

F#入門

まずは命令型的なコードの直移植に役立つ要素から解説し, 関数プログラミング的な最小限の解説をします. 競技プログラミングの実コードが大量にあるため例はほぼ割愛します.

一般的な特徴
(可変な)変数定義
  • (不変な)変数はlet a = 1のようにletで定義します.
  • 可変な変数はlet mutable a = 1mutableをつけて宣言します.
  • 可変な変数<-を使ってa <- 2で書き換えられます.
for
  • ScalaやKotlinと違い, F#でforは式でなく文です.
  • 0から10までカウントアップのループを作るには次のように書きます.
1
2
for i in 0..10 do
  pritfn "%A" i
  • 逆にカウントダウンは次のように書きます.
1
2
for i in 10..(-1)..0 do
  printfn "%A" i
データ構造は配列を例にする
  • F#にも配列・リスト・辞書・集合・キューなどのデータ構造があります.
  • .NET標準・共用の場合もあればF#独自の場合もあります.
  • いちいち全て挙げていては煩雑なため以下の説明では配列で通します.
配列リテラル
  • [|1;2;3|]のように書きます.
    • ちなみにリストは[1;2;3]です.
  • 配列の区切り文字はOCamlと同じくセミコロン;です.
  • [|1..10|]と書くと連番で生成します.
  • [|1..2..9|]と書くと2刻みの配列を生成します.
配列の参照
  • 配列の各要素はa.[i]で取れます.
  • Pythonでのa[i]などと違ってドット(.)が必要です.
  • OCamlの丸括弧流a.(i)とも違います.
  • ちなみにHaskellではデータ型に応じてa !! ia ! i!演算子を使います.
forの二つの役割: 新たな配列作成・結果の積み上げ
  • 競技プログラミングでは配列の処理が重要です.
  • 配列処理用の代表的な関数はmapfoldです.
  • これらの違いを考えるにはforの使い方を考え直す必要があります.
  • forの典型的な使い方は次の二つです.
    • ある配列から新たな配列を作る: map系の処理.
      • 例: 配列の各要素を二倍にする.
    • ある配列から新たな値を作る: fold系の処理.
      • 例: intの配列の和を取る.
      • イメージとしてはたたみ込むループ.
  • 既に説明を書いて添えたように, 上記の二通りの意図・目的でmapfoldを使い分けます.
  • 以下mapfoldを便利に使うためにいくつか準備します.
関数の定義
  • 変数と同じく, 次のようにletで定義します.
1
2
3
4
5
#r "nuget: FsUnit"
open FsUnit

let f x y = x + y
f 2 3 |> should equal 5
  • 引数が複数ある場合, CやPythonなどと違ってカンマ区切りではなくスペース区切りで書きます.
  • 無名関数はfun x y -> x + yのように定義できます.
  • 無名関数も変数に束縛できます.
1
2
3
4
5
#r "nuget: FsUnit"
open FsUnit

let f = fun x y -> x + y
f 2 3 |> should equal 5
  • 再帰関数を定義するときはlet rec f xのようにrecキーワードが必要です.
関数の部分適用
  • F#やHaskellをはじめとしたいわゆる関数型言語では関数の部分適用があります.
  • 有名な例は次の例です.
1
2
3
4
5
6
#r "nuget: FsUnit"
open FsUnit

let plus x y = x + y
let plus2 x = plus 2 x
plus2 3 |> should equal 5
  • 例えばPythonでは関数が第一級のオブジェクトであるため, 返り値として関数を返せます.
  • F#も同じで関数の返り値として関数が返せます.
パイプライン演算子|>
  • F#の特徴的な要素です.
  • 例えばHaskellでも同じ役割のData.Functor.&があるものの, ほとんど見かけません.
  • これを多用するのがF# wayです.
  • Unixコマンドに慣れているならまさにUnixのパイプラインのように扱えます.
  • オブジェクト指向系の言語に慣れているなら, とりあえずはメソッドチェーンのように思えばいいでしょう.
  • 例はこれからmapfoldの中で取り上げます.
  • 実際のコードも参照してください.
  • 次のように考えるとF#のプログラムを書きやすいはずです.
    • 基礎のデータを用意する.
    • 自作コマンドのように書き捨ての関数(無名関数)を作る.
    • Array.mapArray.foldに自作コマンドを部分適用させて処理をつなげる.
map
  • まずはmapを取り上げます.
  • 配列の全ての要素を二倍にする簡単な例は次のように書けます.
1
2
3
4
5
#r "nuget: FsUnit"
open FsUnit

[|1..5|] |> Array.map (fun i -> i*2)
|> should equal [|2;4;6;8;10|]
  • もっとシンプルに次のようにも書けます.
1
2
3
4
5
#r "nuget: FsUnit"
open FsUnit

[|1..5|] |> Array.map ((*) 2)
|> should equal [|2;4;6;8;10|]
  • 一般に演算子を括弧でくくると関数化できるため, それを使って表記を簡潔にしています.
  • ここでまさにパイプライン・部分適用が出てきます.
    • 元データが配列[|1..5|]
    • 各要素を二倍する関数としてfun x -> x*2
    • 全体に適用するのがArray.map
  • これがF#スタイル, ひいては関数プログラミングの基本的な構造です.
  • 部分適用は次の通りです.
    • Array.map: ('T -> 'U) -> 'T array -> 'U array
    • Array.map ((*) 2): 'T array -> 'U array
  • まさに配列を新たな配列に変換する関数ができています.
  • これでmapと自作コマンドとしての無名関数の組み合わせ(部分適用)でデータを処理するフローができました.
fold
  • foldの典型例は配列の総和です.
1
2
[|1..3|] |> Array.fold (fun acc i -> acc + i) 0
|> should equal 6
  • Array.foldは次の構造を持ちます.
    • 型: ('State -> 'T -> 'State ) -> 'State -> 'T array -> 'State
    • 第一引数: 個々の要素を処理する関数
    • 第二引数: 初期値
    • 第三引数: 元データの配列
  • つまり先のコードは元データの[|1..3|]に対して, 型'T array -> 'Stateを持つ関数Array.fold (fun acc i -> acc + i) 0を作用させています.
  • 初期値の'Stateが遠くに0と書かれているとわかりにくいため, F#では次のようにも書けます.
1
2
(0,[|1..3|]) ||> Array.fold (fun acc i -> acc + i)
|> should equal 6
  • Array.foldの前の演算子が||>でパイプライン|が二本になっています.
  • これも慣れると便利です.
  • ちなみにF#には配列の和を取る関数としてArray.sumがあるため, 上の処理は素直にArray.sum [|1..3|]で十分です.
  • もう一つ, 初期値として配列の先頭の値を取りたい場合にはArray.reduceがあります.
1
2
3
4
#r "nuget: FsUnit"
open FsUnit

[|1..3|] |> Array.reduce (+) |> should equal 6
配列処理関数の最後の引数は配列
  • 部分適用で関数をつなげて処理を書きやすくするための大事な特徴です.
  • これに気をつければ部分適用でパイプライン処理がつなげられます.
  • ただしArray.foldBackのような重要な例外があります.
    • OCamlのfold_rightと同じ引数の順番です.
    • Haskellのfoldrとは違います.
    • Haskellコードを読むとき・参考にするときには注意してください.
再帰関数
  • いわゆる関数型言語で真っ先に取り上げられるテーマです.
  • その一方で実際のプログラムでそこまで使われないとも言われる要素です.
  • 実際のプログラムで直接的に再帰がそこまで使われない理由は簡単で, mapfoldを使うからです.
  • そしてmapfoldで書ける処理は再帰でも書けます.
  • むしろmapfoldはパイプライン演算子・部分適用とセットにして処理全体を書きやすくするための再帰の構文糖衣と思うといいでしょう.
  • もちろん再帰でないと書けない・再帰の方が書きやすい処理もあります.
    • 例えばSeqで無限リストを作って処理を回しでもしない限り, 無限ループは再帰でないと書けません.
  • 逆に言えばmapfoldには再帰の強さを制限して無限ループのような嫌な振る舞いをおさえる役割もあります.
  • 再帰関数自体は他の言語にもある概念で, 競技プログラミングでも良く使われる要素でもあるため, これ以上は詳しくは説明しません.
return
  • RubyやScalaと同じくF#は最後に評価した式を返すためreturnは必要ありません.
  • F#のreturnはコンピュテーション式の中で独自の意味を持つため, 下手にreturnを使うとエラーが出ます.
その他の配列処理
  • 個別のコードで確認する方が理解もしやすいため, ここでは最小限の要素だけ説明します.
  • 公式のリファレンスや, それを実行・参照しやすくまとめたReference.fsxも参考にしてください.
  • 配列中の不要な要素を削りたい: filter関数.
  • ある基準をもとにグループ化したい: groupBy関数
  • 和を取りたい: sumまたはsumBy関数
  • ある要素を含むか知りたい: contains関数
  • i番目の要素を取得したい: get関数またはa.[i]
注意
剰余の処理

他の言語のコードを参考にする場合は気をつけるべき要素です.

F#は負の数の剰余を考えると負の数が出てきます. 特にF#では(-1) % 1000 = -1です. 一方例えばPythonでは(-1) % 1000 = 999です. この挙動が問題になる場合は剰余を取りたい数を足してから剰余を取りましょう. 特にF#では((-1) + 1000) % 1000 = 999が得られます.

入出力の基礎

注意

私は以下のテンプレートを使っています. 注意を箇条書きにします.

  • テンプレートそのままではエディタのエラーが出ます. 使うたびに必要なところだけ見繕って削ってください.
  • 競技プログラミングではintの範囲でおさまらない処理が多いため, int64を基本にするのが無難です.
  • コードの確認用に簡単なテストを実行できるようにしていて, 最初の#r "nuget: FsUnit"と最後のshould equalはテストライブラリの読み込みと実行です. 提出用コードには含めません.

入門レベルの入出力なら下記コードの多少のバリエーションでまかなえます. 私の勉強が進んでもっと凝った入出力テンプレートが必要になってきたら追記・修正します.

記号選択
  • AtCoderの問題文でよく大文字が出てくるため, F#の流儀よりもAtCoderの文章との対応を重視してNMを使っています.
  • リストはXs, 配列はarrayからXa, シーケンスはXqのように書きます. リストにxsをあてるのは少なくともHaskellでは標準的な記法で, それを採用しています. そしてF#はasが予約語で自由に使えないため, 変数名の先頭を大文字にしているのは変数名Asを使いやすくするための処置でもあります.
テンプレート
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#r "nuget: FsUnit"
open FsUnit

System.IO.Directory.SetCurrentDirectory __SOURCE_DIRECTORY__

let solve Xa =
  let MOD = 1_000_000_007L
  System.Int32.MaxValue
  System.Int64.MaxValue
  1

let N = stdin.ReadLine() |> int64
let M = stdin.ReadLine() |> bigint.Parse
let D,X = stdin.ReadLine().Split() |> Array.map int64 |> (fun x -> x.[0],x.[1])
let Xa = stdin.ReadLine().Split() |> Array.map int64
let Aa = Array.init N (stdin.ReadLine() |> int64)
let Aa = Array.init N (fun _ -> stdin.ReadLine().Split() |> Array.map int64 |> fun x -> x.[0],x.[1])
let Alls = Console.ReadLine() |> Seq.initInfinite |> Seq.takeWhile ((<>) null)
let S = stdin.ReadLine()
solve Xa |> Array.map string |> String.concat " " |> stdout.WriteLine
solve Xa |> Array.iter stdout.WriteLine
solve Xa |> stdout.WriteLine

solve Aa |> should equal 1