No.2908 Strange Online Judge (Extra)
タグ : / 解いたユーザー数 1
作問者 : 👑 Mizar / テスター : 👑 獅子座じゃない人
問題文
あなたは、オンラインジャッジ「StrOnJ」を使用したプログラミングコンテストに参加しています。問題文は以下のようなものです。
問題
非負整数の集合 $S$ について、 $\operatorname{mex}{S}$ を「 $S$ に含まれない最小の非負整数」とします。
厳密には、非負整数全体の集合 $\mathbb{N}$ と非負整数の集合 $S$ について、 $\operatorname{mex}{S}$ を $\operatorname{mex}{S}=\min(\mathbb{N}\setminus S)$ で定義します。
正の整数 $K$ と 非負整数 $M$ ( ただし $0\leq M\leq K$ ) が与えられたとき、数列 $(a_n)$ を次のように再帰的に定めます。
- $a_1=1$
- $a_{n+1}=\operatorname{mex}(\{0\}\cup\{a_i\ |\ 1\leq i\leq n\}\cup\{a_i+Ki-M+1\ |\ 1\leq i\leq n\})\ (n\geq 1)$
$a_N$ を求めてください。
制約
- 入力は全て整数
- $1\leq K\leq 10^9$
- $0\leq M\leq K$
- $1\leq N\leq 10^9$
ただし、StrOnJでは、この問題のために作られたプログラミング言語である「StLang」のソースコード以外提出することができません。
以下に示すStLangの仕様に従って、 $a_N$ を求めることができるStLangのソースコードを出力してください。ジャッジの都合上、出力の行数は1000行以下にしてください。
入力
問題としての入力は存在しません。
ジャッジでは、出力されたプログラムに対して $1\leq K\leq 10^9,\ 0\leq M\leq K,\ 1\leq N\leq 10^9$ を満たす整数 $K,M,N$ が与えられます。
出力
$a_N$ を求めることができるStLangのソースコードを出力してください。出力の行数は1000行以下にしてください。
ジャッジについて
提出されたプログラムの実行時エラーと区別するため、実行制限とメモリ制限を守って出力が行われた場合、ジャッジの結果はACまたはWAとなります。
StLangの仕様
サンプルコード
StLang で 二項係数 ${}_NC_K\quad(N\leq 62)$ を計算するサンプルコードです。それぞれ、空白・コメント文字列を使用しない例・使用した例です。
ifN>=K $0=1 $1=1 $2=N while$1<=K $0=$0*$2 $0=$0/$1 $2=$2-1 $1=$1+1 end end return$0
# 二項係数 nCk の計算 (N > 62 の時は演算オーバーフローする可能性あり) if N >= K # N < K の時の出力は 0 とする $0 = 1 $1 = 1 $2 = N while $1 <= K # for $1 in 1..=K $0 = $0 * $2 # $2 == N + 1 - $1 $0 = $0 / $1 # ここで割り算の余りは生じない $2 = $2 - 1 $1 = $1 + 1 end end return $0 # return 行より後のコメント行も可
StLang のソースコードを直接書いた場合、 言語に Text (cat 8.3)
を選択することで、そのまま提出することができます。
StLang 仕様の概要
StLang の主な仕様
- 問題「Strange Online Judge」との仕様の相違は、定数$M$ を 値
M
として追加で扱えるようになった点です。 - StLang では $0$ 以上 $2^{64}$ 未満 の整数のみ扱うことができます。
- 変数は
$0
~$9
の10個のみ使えます。各テストケース毎の実行開始時、$0
~$9
の変数は $0$ で初期化されます。 - テストケースごとに与えられる定数$K$、定数$M$、定数$N$を値
K
,M
,N
として使うことができます。 - 算術演算(
+
,-
,*
,/
,%
)は代入文の中で最大1回のみ扱えます。演算の結合はできません。 - 制御構文として使える
if
文,while
文 の条件式では、2つの値の比較演算(==
,!=
,>=
,<=
,>
,<
)のみ扱います。 #
文字以降行末までコメントとして扱われます。- 複数文字のキーワード(
if
,while
,end
,return
,==
,!=
,>=
,<=
)及び10進整数値の文字列の途中を除き、半角スペースは無視されます。
- 問題「Strange Online Judge」との仕様の相違は、定数$M$ を 値
-
特筆すべき注意点
- 10進整数値、定数
K
、定数M
、定数N
、変数$0
~$9
以外のリテラル・定数・変数は利用できません。 - 1000行を超えるプログラムを出力すると WA となります。1行に複数の文を含めることはできません。
- 1ケースにおける代入操作と比較演算の合計実行回数 $C$ が $1000$ を超えると WA となります。
- 10進整数値・算術演算(
+
,-
,*
,/
,%
)にて $0$ 未満 の値、 $2^{64}$ 以上 の値、 $0$ 除算 が発生すると WA となります。 - 制御構文 (
if
,while
) ではend
にてブロック文の終了を対になるように明示することが必要です。 return
文 はプログラムの最後に1回だけ記述する必要があります。制御構文のブロック内にreturn
文 を含める事はできません。- 制御構文 (
if
,while
) における条件式 やreturn
文 の返り値 の中に算術演算(+
,-
,*
,/
,%
)を含める事はできません。
- 10進整数値、定数
構文・処理の詳細
(ここをクリックで開閉します)
入力されたプログラムを構文解析し、下図のような構文木を構築し(必ずしも実行に必要としないノードは構築を省略する場合あり)、入力ケースごとに変数・定数の初期値が設定され、この構文木を(制御構文を除き、基本的には)根から順番に辿るように実行します。
StLang の実行環境サンプル(後述)は、 Lark を用いて実装しています。
Lark - a parsing toolkit for Python : https://github.com/lark-parser/lark
入力されたプログラムから構文木への変換は以下のようなルールで行われます。
EBNF(拡張バッカス・ナウア記法) + 正規表現 + α による(パーサジェネレータ Lark 向け)StLang構文定義:
?start: program program: statements "return" value lineends ?value: number | const_k | const_m | const_n | variable number: /[0-9]+/ const_k: "K" const_m: "M" const_n: "N" variable: "$" var_ident var_ident: /[0-9]/ ?statements: statement* ?statement: assignment | branching | repeating | lineend assignment: "$" var_ident "=" expression lineend ?expression: value | add | sub | mul | div | rem add: value "+" value sub: value "-" value mul: value "*" value div: value "/" value rem: value "%" value branching: "if" condition lineend statements "end" lineend repeating: "while" condition lineend statements "end" lineend ?condition: eq | ne | ge | le | gt | lt eq: value "==" value ne: value "!=" value ge: value ">=" value le: value "<=" value gt: value ">" value lt: value "<" value ?lineends: lineend* ST_COMMENT? ?lineend: ST_COMMENT? CR? LF ST_COMMENT: "#" /[^\n]*/ CR : /\r/ LF : /\n/ WS_INLINE: (" "|/\t/)+ %ignore WS_INLINE
構文木上でのプログラムの実行は、以下のような実装で行われます。
パーサジェネレータ Lark によるインタプリタ全体の実装例は、後の「StLang @ Lark 実行環境サンプル」を参照してください。
パーサジェネレータ Lark によるインタプリタの実装例:
class StLangInterpreter(lark.visitors.Interpreter): """StLang Interpreter""" def __init__(self, k=0, m=0, n=0) -> None: super().__init__() assert 0 <= int(k) < 2**64 and 0 <= int(m) < 2**64 and 0 <= int(n) < 2**64, "OutOfRangeNumber" self.env = { 'C': 0, 'K': int(k), 'M': int(m), 'N': int(n), '0': 0, '1': 0, '2': 0, '3': 0, '4': 0, '5': 0, '6': 0, '7': 0, '8': 0, '9': 0 } def program(self, tree) -> int: self.visit(tree.children[0]) # statements return self.visit(tree.children[1]) # value def number(self, tree) -> int: ret = int(tree.children[0].value) assert 0 <= ret < 2**64, "OutOfRangeNumber" return ret def const_k(self, _tree) -> int: return self.env['K'] def const_m(self, _tree) -> int: return self.env['M'] def const_n(self, _tree) -> int: return self.env['N'] def variable(self, tree) -> int: return self.env[self.visit(tree.children[0])] def var_ident(self, tree) -> str: return str(tree.children[0].value) def assignment(self, tree) -> None: self.env['C'] += 1 assert self.env['C'] <= 1000, "StepLimitExceed" self.env[self.visit(tree.children[0])] = self.visit(tree.children[1]) def add(self, tree) -> int: ret = self.visit(tree.children[0]) + self.visit(tree.children[1]) assert 0 <= ret < 2**64, "OutOfRangeNumber" return ret def sub(self, tree) -> int: ret = self.visit(tree.children[0]) - self.visit(tree.children[1]) assert 0 <= ret < 2**64, "OutOfRangeNumber" return ret def mul(self, tree) -> int: ret = self.visit(tree.children[0]) * self.visit(tree.children[1]) assert 0 <= ret < 2**64, "OutOfRangeNumber" return ret def div(self, tree) -> int: lhs, rhs = self.visit(tree.children[0]), self.visit(tree.children[1]) assert 0 < rhs, "OutOfRangeNumber" return lhs // rhs def rem(self, tree) -> int: lhs, rhs = self.visit(tree.children[0]), self.visit(tree.children[1]) assert 0 < rhs, "OutOfRangeNumber" return lhs % rhs def branching(self, tree) -> None: self.env['C'] += 1 assert self.env['C'] <= 1000, "StepLimitExceed" if self.visit(tree.children[0]): # condition self.visit(tree.children[2]) # statements def repeating(self, tree) -> None: while True: self.env['C'] += 1 assert self.env['C'] <= 1000, "StepLimitExceed" if not self.visit(tree.children[0]): # condition break self.visit(tree.children[2]) # statements def eq(self, tree) -> bool: return self.visit(tree.children[0]) == self.visit(tree.children[1]) def ne(self, tree) -> bool: return self.visit(tree.children[0]) != self.visit(tree.children[1]) def ge(self, tree) -> bool: return self.visit(tree.children[0]) >= self.visit(tree.children[1]) def le(self, tree) -> bool: return self.visit(tree.children[0]) <= self.visit(tree.children[1]) def gt(self, tree) -> bool: return self.visit(tree.children[0]) > self.visit(tree.children[1]) def lt(self, tree) -> bool: return self.visit(tree.children[0]) < self.visit(tree.children[1])
StLang の構文と処理の解説と、 EBNF(拡張バッカス・ナウア記法) + 正規表現 を主に用いた構文定義、Lark によるインタプリタの実装例を併記します。
ソースコード全体は $1000$ 行以下の構文 program
で記述されていなければなりません。
ソースコードが以下のように再帰的に定義された、正しい構文で記述されていない場合、WAとなります。
プログラムを実行するコンピュータは、非負整数の列 $(V_0,V_1,V_2,\ldots,V_9)$ および非負整数 $C$ をもつとします。
テストケース毎に、テストケースから与えられた $K,M,N$ の値が定数値K
,M
,N
に設定され、
変数 $V_0$~$V_9$ ($0
~$9
) の値は $0$ に初期化され、実行ステップカウンター $C$ の値は $0$ に初期化されます。
実行中に $C$ の値が $1000$ より大きくなった場合、WAとなります。
def __init__(self, k=0, m=0, n=0) -> None: super().__init__() assert 0 <= int(k) < 2**64 and 0 <= int(m) < 2**64 and 0 <= int(n) < 2**64, "OutOfRangeNumber" self.env = { 'C': 0, 'K': int(k), 'M': int(m), 'N': int(n), '0': 0, '1': 0, '2': 0, '3': 0, '4': 0, '5': 0, '6': 0, '7': 0, '8': 0, '9': 0 }
構文定義の行頭に ?
が付いているのは、構文木上でノードの構成を省略するものに付けられたもので、ここでは、子要素の実装をそのまま用いるため、それ自体では直接の機能実装が行われないものに適用しています。
"
~"
で囲まれている部分は、字句文字列である事を表す
/
~/
で囲まれている部分は、正規表現による文字列マッチングの指定
?
を後に付けた場合、 0回~1回 繰り返されることを示す(つまり、省略可能である事を示す)
*
を後に付けた場合、 0回以上 繰り返されることを示す(省略可能な繰り返し)
+
を後に付けた場合、 1回以上 繰り返されることを示す(繰り返し)
|
で併記した場合、いずれか1つが選ばれること (choice) を示す
/[0-9]+/
の正規表現は、数字1文字以上の文字列に当てはまることを示す
/[0-9]/
の正規表現は、数字1文字の文字列に当てはまることを示す
"#" /[^\n]*/
は、 #
文字から始まる、改行文字を含まない任意長の文字列に当てはまることを示す
/\r/
の正規表現は、キャートリッジリターン制御文字に当てはまることを示す
/\n/
の正規表現は、改行文字に当てはまることを示す
(" "|/\t/)+
は、半角空白文字もしくは水平タブ文字1文字以上に当てはまることを示す
-
start
: 【概要】構文木のroot nodeの指定 【処理】program
の実行?start: program
-
program
: 【概要】プログラム全体の定義 【処理】statements
を実行した後にvalue
の値を出力して終了するprogram: statements "return" value lineends
def program(self, tree) -> int: self.visit(tree.children[0]) # statements return self.visit(tree.children[1]) # value
-
value
: 【概要】整数値 【構文】number
,const_k
,const_n
,variable
のいずれか 【処理】いずれか対応する値を返す?value: number | const_k | const_m | const_n | variable
-
number
: 【概要】10進整数値 【例外】値が $0$ 以上 $2^{64}-1=18446744073709551615$ 以下でなければ WAconst_k
: 【概要】定数 $K$ 【処理】テストケース毎に決まった値を返すconst_m
: 【概要】定数 $M$ 【処理】テストケース毎に決まった値を返すconst_n
: 【概要】定数 $N$ 【処理】テストケース毎に決まった値を返すvariable
: 【概要】変数値【処理】変数 $V_0$ ~ $V_9$ のうち、var_ident
に対応する値を返すvar_ident
: 【概要】変数識別子【構文】1桁の整数【処理】$V_0$ ~ $V_9$ の変数への対応を示すnumber: /[0-9]+/ const_k: "K" const_m: "M" const_n: "N" variable: "$" var_ident var_ident: /[0-9]/
def number(self, tree) -> int: ret = int(tree.children[0].value) assert 0 <= ret < 2**64, "OutOfRangeNumber" return ret def const_k(self, _tree) -> int: return self.env['K'] def const_m(self, _tree) -> int: return self.env['M'] def const_n(self, _tree) -> int: return self.env['N'] def variable(self, tree) -> int: return self.env[self.visit(tree.children[0])] def var_ident(self, tree) -> str: return str(tree.children[0].value)
-
statements
: 【概要】ブロック文【構文】0行以上のstatement
からなる 【処理】 含まれるstatement
ごとに順番に実行する?statements: statement*
-
statement
: 【概要】文【構文】assignment
,branching
,repeating
, 空行(もしくはコメント行) のいずれかからなる 【処理】対応するものを実行する?statement: assignment | branching | repeating | lineend
-
assignment
: 【概要】代入【処理】変数
var_ident
に、expression
を評価した値(符号なし64bit整数)を代入する代入が行われる毎に $C$ の値が $1$ 加算される【例外】$C$ の値が $1000$ より大きくなると WA
assignment: "$" var_ident "=" expression lineend
def assignment(self, tree) -> None: self.env['C'] += 1 assert self.env['C'] <= 1000, "StepLimitExceed" self.env[self.visit(tree.children[0])] = self.visit(tree.children[1])
-
expression
: 【概要】整数値の評価【処理】value
,add
,sub
,mul
,div
,rem
のいずれか対応するものを評価した値を返す?expression: value | add | sub | mul | div | rem
-
add
: 【概要】加算 $a+b$ 【例外】結果の値が $0$ 以上 $2^{64}-1=18446744073709551615$ 以下でなければ WAsub
: 【概要】減算 $a-b$ 【例外】結果の値が $0$ 以上 $2^{64}-1=18446744073709551615$ 以下でなければ WAmul
: 【概要】乗算 $a\times b$ 【例外】結果の値が $0$ 以上 $2^{64}-1=18446744073709551615$ 以下でなければ WAdiv
: 【概要】除算 $\lfloor a/b\rfloor$ 【例外】除数が $0$ だと WArem
: 【概要】剰余算 $a-b\lfloor a/b\rfloor$ 【例外】除数が $0$ だと WAadd: value "+" value sub: value "-" value mul: value "*" value div: value "/" value rem: value "%" value
def add(self, tree) -> int: ret = self.visit(tree.children[0]) + self.visit(tree.children[1]) assert 0 <= ret < 2**64, "OutOfRangeNumber" return ret def sub(self, tree) -> int: ret = self.visit(tree.children[0]) - self.visit(tree.children[1]) assert 0 <= ret < 2**64, "OutOfRangeNumber" return ret def mul(self, tree) -> int: ret = self.visit(tree.children[0]) * self.visit(tree.children[1]) assert 0 <= ret < 2**64, "OutOfRangeNumber" return ret def div(self, tree) -> int: lhs, rhs = self.visit(tree.children[0]), self.visit(tree.children[1]) assert 0 < rhs, "OutOfRangeNumber" return lhs // rhs def rem(self, tree) -> int: lhs, rhs = self.visit(tree.children[0]), self.visit(tree.children[1]) assert 0 < rhs, "OutOfRangeNumber" return lhs % rhs
-
branching
: 【概要】分岐(if)【処理】
condition
の評価(bool) がtrue
であればstatements
を実行するcondition
が評価される時に $C$ の値が $1$ 加算される 【例外】$C$ の値が $1000$ より大きくなると WAbranching: "if" condition lineend statements "end" lineend
def branching(self, tree) -> None: self.env['C'] += 1 assert self.env['C'] <= 1000, "StepLimitExceed" if self.visit(tree.children[0]): # condition self.visit(tree.children[2]) # statements
-
repeating
: 【概要】繰り返し(while)【処理】
condition
の評価(bool) がtrue
である間statements
を繰り返し実行するcondition
が評価される毎に $C$ の値が $1$ 加算される 【例外】$C$ の値が $1000$ より大きくなると WArepeating: "while" condition lineend statements "end" lineend
def repeating(self, tree) -> None: while True: self.env['C'] += 1 assert self.env['C'] <= 1000, "StepLimitExceed" if not self.visit(tree.children[0]): # condition break self.visit(tree.children[2]) # statements
-
condition
: 【概要】比較演算 【処理】eq
,ne
,ge
,le
,gt
,lt
のいずれか対応するものの評価(bool)を返す?condition: eq | ne | ge | le | gt | lt
-
eq
: 【概要】EQual 【処理】2つのvalue
の値が等しいならtrue
、さもなくばfalse
ne
: 【概要】Not Equal 【処理】2つのvalue
の値が異なるならtrue
、さもなくばfalse
ge
: 【概要】Greater than or Equal to 【処理】1番目のvalue
は 2番目のvalue
以上ならtrue
、さもなくばfalse
le
: 【概要】Less than or Equal to 【処理】1番目のvalue
は 2番目のvalue
以下ならtrue
、さもなくばfalse
gt
: 【概要】Greater Than 【処理】1番目のvalue
は 2番目のvalue
より 大なり ならtrue
、さもなくばfalse
lt
: 【概要】Less Than 【処理】1番目のvalue
は 2番目のvalue
より 小なり ならtrue
、さもなくばfalse
eq: value "==" value ne: value "!=" value ge: value ">=" value le: value "<=" value gt: value ">" value lt: value "<" value
def eq(self, tree) -> bool: return self.visit(tree.children[0]) == self.visit(tree.children[1]) def ne(self, tree) -> bool: return self.visit(tree.children[0]) != self.visit(tree.children[1]) def ge(self, tree) -> bool: return self.visit(tree.children[0]) >= self.visit(tree.children[1]) def le(self, tree) -> bool: return self.visit(tree.children[0]) <= self.visit(tree.children[1]) def gt(self, tree) -> bool: return self.visit(tree.children[0]) > self.visit(tree.children[1]) def lt(self, tree) -> bool: return self.visit(tree.children[0]) < self.visit(tree.children[1])
-
lineends
: 【概要】return
文より後のprogram
末尾【構文】0回以上のlineend
、 1回以下の改行なしコメント文字列 【処理】何の処理も行わない?lineends: lineend* ST_COMMENT?
-
lineend
: 【概要】行末(コメント文字列が含まれる場合あり) 【構文】ST_COMMENT
,CR
は省略可【処理】何の処理も行わない?lineend: ST_COMMENT? CR? LF
-
ST_COMMENT
: 【概要】コメント文字列【構文】"#" 文字以降、改行文字を除く行末までをコメント文字列と見なす【処理】何の処理も行わないST_COMMENT: "#" /[^\n]*/
-
CR
: 【概要】キャートリッジリターン制御文字【処理】何の処理も行わないLF
: 【概要】改行文字【処理】何の処理も行わないCR : /\r/ LF : /\n/
-
WS_INLINE
: 【概要】行内の空白文字類【構文】半角空白、水平タブ制御文字 のいずれか【処理】何の処理も行わないWS_INLINE: (" "|/\t/)+
-
【構文】行内の空白文字類を無視
%ignore WS_INLINE
字句
(ここをクリックで開閉します)
以下の字句を使用することができます。ただし、[0-9]
は任意の1文字の数字を、[0-9]+
は1文字以上の任意の数字の列を、.*
は0文字以上の改行を除く任意の文字の列を、\n
は改行を表します。
[0-9]+
K
M
N
$
[0-9]
+
-
*
/
%
if
while
end
==
!=
>=
<=
>
<
=
return
#
.*
\n
また、半角スペース(
)は無視されます。改行は無視されないことに注意してください。
構文
(ここをクリックで開閉します)
構文は以下のように再帰的に定義されます。ソースコード全体は構文program
で記述されていなければなりません。
上で定義した字句はダブルクォーテーションで囲んで表すこととし、字句または構文の結合を空白区切りで表すものとします。また、は空文字列であるとします。
program
は、以下のように定義される。statements
"return"
value
lineends
statements
は、以下のいずれかである。statement
statements
statement
は、以下のいずれかである。lineend
assignment
branching
assignment
は、以下のように定義される。"$"
"[0-9]"
"="
expression
lineend
branching
は、以下のように定義される。control
condition
lineend
statements
"end"
lineend
control
は、以下のいずれかである。"if"
"while"
expression
は、以下のいずれかである。value
value
"+"
value
value
"-"
value
value
"*"
value
value
"/"
value
value
"%"
value
condition
は、以下のいずれかである。value
"=="
value
value
"!="
value
value
">="
value
value
"<="
value
value
">"
value
value
"<"
value
value
は、以下のいずれかである。number
"K"
"M"
"N"
variable
number
は、以下のものである。"[0-9]+"
variable
は、以下のものである。"$"
var_ident
var_ident
は、以下のものである。"[0-9]"
lineends
は、以下のいずれかである。"#"
".*"
lineend
lineends
lineend
は、以下のいずれかである。"\n"
"#"
".*"
"\n"
処理
(ここをクリックで開閉します)
ソースコードが正しい構文で記述されていない場合、WAとなります。
次に、number
を数値リテラルとして評価します。記述された数字の列を非負整数として解釈したとき $0$ 以上 $2^{64}-1=18446744073709551615$ 以下でなければ、WAとなります。
プログラムを実行するコンピュータは、非負整数の列 $(V_0,V_1,V_2,\ldots,V_9)$ および非負整数 $C$ をもつとします。初期値は全て $0$ です。
"K"
は $K$ の値、 "M"
は $M$ の値、 "N"
は $N$ の値を表す定数です。これらの値は各ケースごとに決まります。
variable
について、 $i$ を数字としたとき、"$i"
は $V_i$ の値を表す変数です。
number
、"K"
、"M"
、"N"
、variable
の値は、value
に引き継がれます。
値 $a$ をもつvalue
であるa
と値 $b$ をもつvalue
であるb
を考えます。a
operator
b
の形であるexpression
について、その値は
operator
が"+"
の場合、 $a+b$operator
が"-"
の場合、 $a-b$operator
が"*"
の場合、 $a \times b$operator
が"/"
の場合、 $\lfloor a / b\rfloor$operator
が"%"
の場合、 $a-b\lfloor a / b\rfloor$
また、a
comparator
b
の形であるcondition
は
comparator
が"=="
の場合、 $a=b$ であるかどうかcomparator
が"!="
の場合、 $a\neq b$ であるかどうかcomparator
が">="
の場合、 $a\geq b$ であるかどうかcomparator
が"<="
の場合、 $a\leq b$ であるかどうかcomparator
が">"
の場合、 $a\gt b$ であるかどうかcomparator
が"<"
の場合、 $a\lt b$ であるかどうか
プログラムはprogram
のstatements
で記述された処理を行った後に、value
として指定された値を返して終了します。返された値が与えられた $K,M,N$ に対する $a_N$ でない場合、WAとなります。
lineend
と endcomment
については、内容は無視され、何も処理を行いません。
statements
について、の形であれば何も処理を行いません。
statement
statements
の形であれば、statement
で記述された処理を行った後、statements
の処理に移ります。
assignment
は、variable
の var_ident
に対応する $V_i$ の値をexpression
のもつ値に変更します。また、 $C$ の値が $1$ 大きくなります。
"if"
condition
statements
"end"
の形のbranching
について、まずcondition
の値にかかわらず、 $C$ の値が $1$ 大きくなります。
次に、condition
の値が真である場合のみstatements
で記述された処理が行われます。
"while"
condition
statements
"end"
の形のbranching
について、condition
の値が真である限り、 $C$ の値を $1$ 大きくしてstatements
で記述された処理を行うことを繰り返します。
condition
の値が偽になった場合、 $C$ の値が $1$ 大きくなり、このbranching
の処理は終了します。
$C$ の値が $1000$ より大きくなった場合、WAとなります。( $1000$ で妥当であるかは要検証)
StLang @ Lark 実行環境サンプル
デフォルトでは StLang のサンプルコード が設定されているので、問題文に合ったプログラムに適宜書き換えてお使いください。
-
Pythonコード(簡易版) (ここをクリックで開閉します)
"""StLang@Lark 実行環境サンプル 簡易版""" """こちらは Lark モジュールのインストールが必要です""" """ Lark - a parsing toolkit for Python : https://github.com/lark-parser/lark """ #%pip install lark>=1.1.9 --upgrade import lark ### TODO: ここからを本番の問題用の正しいコードに書き換える PROGRAM = r""" # サンプルプログラム: 二項係数 nCk の計算 (N > 62 の時は演算オーバーフローする可能性あり) if N >= K # N < K の時の出力は 0 とする $0 = 1 $1 = 1 $2 = N while $1 <= K # for $1 in 1..=K $0 = $0 * $2 # $2 == N + 1 - $1 $0 = $0 / $1 # ここで割り算の余りは生じない $2 = $2 - 1 $1 = $1 + 1 end end return $0 # return 行より後のコメント行も可 """ ### TODO: ここまでを本番の問題用の正しいコードに書き換える import itertools import random import sys """ランダムテストケースの生成""" def gen_test(_: int) -> tuple[int, int, int]: k = min(random.randrange(1, 2**random.randrange(1, 32)), 10**9) m = random.randrange(0, k+1) n = min(random.randrange(1, 2**random.randrange(1, 32)), 10**9) ### TODO: k,m,n の値から回答すべき値 an を生成するコードを書く an = 0 return k, m, n, an ### TODO: ランダムテストケースの生成を正しく書いた後、ランダムテストケース生成数を書き換える """ランダムテストケースの生成数""" GEN_CASES = 0 def main() -> None: stlang_parser = lark.Lark( STLANG_GRAMMER, parser='lalr', ) assert len(PROGRAM.splitlines()) <= 1000 stlang_tree = stlang_parser.parse(PROGRAM) for k, m, n, an in itertools.chain([ # 本番問題用のサンプルケース ( 3, 1, 5, 6 ), ( 8, 1, 1, 1 ), ( 10, 1, 1000000000, 1099019513 ), ( 3, 0, 5, 6 ), ( 8, 8, 1, 1 ), ( 10, 6, 1000000000, 1099019514 ), ], map(gen_test, range(GEN_CASES))): assert 1 <= k <= 10**9 and 1 <= n <= 10**9 and 0 <= an < 2**64 result, judge, worker = None, None, StLangInterpreter(k, n) try: result = worker.visit(stlang_tree) judge = "Accepted" if result == an else "WrongAnswer" except AssertionError as e: judge, tb = "RuntimeError", e.__traceback__ print(type(e), e, file=sys.stderr) while tb is not None: print(tb.tb_frame, file=sys.stderr) tb = tb.tb_next print(f"{judge}: K={k} N={n} a_N={an} prog_return:{result} C:{worker.env['C']}") STLANG_GRAMMER = r""" ?start: program program: statements "return" value lineends ?value: number | const_k | const_m | const_n | variable number: /[0-9]+/ const_k: "K" const_m: "M" const_n: "N" variable: "$" var_ident var_ident: /[0-9]/ ?statements: statement* ?statement: assignment | branching | repeating | lineend assignment: "$" var_ident "=" expression lineend ?expression: value | add | sub | mul | div | rem add: value "+" value sub: value "-" value mul: value "*" value div: value "/" value rem: value "%" value branching: "if" condition lineend statements "end" lineend repeating: "while" condition lineend statements "end" lineend ?condition: eq | ne | ge | le | gt | lt eq: value "==" value ne: value "!=" value ge: value ">=" value le: value "<=" value gt: value ">" value lt: value "<" value ?lineends: lineend* ST_COMMENT? ?lineend: ST_COMMENT? CR? LF ST_COMMENT: "#" /[^\n]*/ CR : /\r/ LF : /\n/ WS_INLINE: (" "|/\t/)+ %ignore WS_INLINE """ class StLangInterpreter(lark.visitors.Interpreter): """StLang Interpreter""" def __init__(self, k=0, n=0) -> None: super().__init__() assert 0 <= int(k) < 2**64 and 0 <= int(n) < 2**64, "OutOfRangeNumber" self.env = { 'C': 0, 'K': int(k), 'M': int(m), 'N': int(n), '0': 0, '1': 0, '2': 0, '3': 0, '4': 0, '5': 0, '6': 0, '7': 0, '8': 0, '9': 0 } def program(self, tree) -> int: self.visit(tree.children[0]) return self.visit(tree.children[1]) def number(self, tree) -> int: ret = int(tree.children[0].value) assert 0 <= ret < 2**64, "OutOfRangeNumber" return ret def const_k(self, _tree) -> int: return self.env['K'] def const_m(self, _tree) -> int: return self.env['M'] def const_n(self, _tree) -> int: return self.env['N'] def variable(self, tree) -> int: return self.env[self.visit(tree.children[0])] def var_ident(self, tree) -> str: return str(tree.children[0].value) def assignment(self, tree) -> None: self.env['C'] += 1 assert self.env['C'] <= 1000, "StepLimitExceed" self.env[self.visit(tree.children[0])] = self.visit(tree.children[1]) def add(self, tree) -> int: ret = self.visit(tree.children[0]) + self.visit(tree.children[1]) assert 0 <= ret < 2**64, "OutOfRangeNumber" return ret def sub(self, tree) -> int: ret = self.visit(tree.children[0]) - self.visit(tree.children[1]) assert 0 <= ret < 2**64, "OutOfRangeNumber" return ret def mul(self, tree) -> int: ret = self.visit(tree.children[0]) * self.visit(tree.children[1]) assert 0 <= ret < 2**64, "OutOfRangeNumber" return ret def div(self, tree) -> int: lhs, rhs = self.visit(tree.children[0]), self.visit(tree.children[1]) assert 0 < rhs, "OutOfRangeNumber" return lhs // rhs def rem(self, tree) -> int: lhs, rhs = self.visit(tree.children[0]), self.visit(tree.children[1]) assert 0 < rhs, "OutOfRangeNumber" return lhs % rhs def branching(self, tree) -> None: self.env['C'] += 1 assert self.env['C'] <= 1000, "StepLimitExceed" if self.visit(tree.children[0]): # condition self.visit(tree.children[2]) # statements def repeating(self, tree) -> None: while True: self.env['C'] += 1 assert self.env['C'] <= 1000, "StepLimitExceed" if not self.visit(tree.children[0]): # condition break self.visit(tree.children[2]) # statements def eq(self, tree) -> bool: return self.visit(tree.children[0]) == self.visit(tree.children[1]) def ne(self, tree) -> bool: return self.visit(tree.children[0]) != self.visit(tree.children[1]) def ge(self, tree) -> bool: return self.visit(tree.children[0]) >= self.visit(tree.children[1]) def le(self, tree) -> bool: return self.visit(tree.children[0]) <= self.visit(tree.children[1]) def gt(self, tree) -> bool: return self.visit(tree.children[0]) > self.visit(tree.children[1]) def lt(self, tree) -> bool: return self.visit(tree.children[0]) < self.visit(tree.children[1]) main()
- ソースコード: Python (スタンドアロン版、Larkインストール不要) ソースコードをコピーして yukicoderオンライン実行 上で実行可能です
- ソースコード: Jupyter Notebook (VSCode + Jupyter Notebook拡張など、ローカル環境向け)
- オンライン実行環境: Google Colab (要 Google アカウント)
サンプル
ジャッジが定数として与える $K\ M\ N$ と、返すべき値のサンプルです。「定数」の $3$ つの数は順に $K\ M\ N$ です。
サンプル1
定数
3 0 5
返すべき値
6
$K=3,\ M=0,\ N=5$ です。
$a_1=1,\ a_2=\operatorname{mex}\{0,1,5\}=2,\ a_3=\operatorname{mex}\{0,1,2,5,9\}=3,\ a_4=\operatorname{mex}\{0,1,2,3,5,9,13\}=4,\ a_5=\operatorname{mex}\{0,1,2,3,4,5,9,13,17\}=6$ より、解は $6$ です。
サンプル2
定数
8 8 1
返すべき値
1
サンプル3
定数
10 6 1000000000
返すべき値
1099019514
提出するには、Twitter 、GitHub、 Googleもしくは右上の雲マークをクリックしてアカウントを作成してください。