ABC299 参加記録
ABC299に参加しました。A~Dの解説を書いていきます。
A - Treasure Chest
問題概要
3 種類の文字
.
|
*
からなる、長さの文字列 N が与えられます。 S
には S |
がちょうどつ、 2 *
がちょうどつ含まれます。 1
つの 2 |
で囲まれた部分の中に*
が含まれるか判定してください。
含まれている場合in
と、含まれていない場合out
と出力してください。
解法
今までに現れた|
の個数を持つ変数
-
に対して:i=1,2,\dots,N -
かつc=1 S_i= *
ならば、in
を出力して終了する
-
最後まで終えたならout
を出力する。
実装例
#include <iostream>
#include <string>
using namespace std;
int main() {
int n;
string s;
cin >> n >> s;
int c = 0;
for (int i = 0; i < n; i++) {
if (s[i] == '|') c++;
if (c == 1 && s[i] == '*') {
puts("in");
return 0;
}
}
puts("out");
return 0;
}
B - Trick Taking
問題概要
から 1 までの番号のついた N 人のプレイヤーがカードゲームをします。 N
各プレイヤーは、カードを一枚場に出します。
各カードには、色,値のふたつの属性を持っています。
各について、プレイヤー i の出したカードの色は i ,値は C_i です。 R_i
今から、以下の方法で人の勝者を決めます: 1
- 色が
であるカードが T 枚以上場に出された場合、色が 1 であるカードのうち値が最大のものを出したプレイヤーが勝者である。 T - 色が
であるカードが場に T 枚も出されなかった場合、プレイヤー 1 が出したカードと同じ色のカードのうち値が最大のものを出したプレイヤーが勝者である。(プレイヤー 1 自身も勝者となり得ることに注意してください。) 1 勝者を決定してください。
解法
なんかややこしいが、
なる C_i=T が存在しなければ i と書き換える。 T\leftarrow C_1
という操作を挟むと、勝者の決め方は1.の方を使うだけで済む。
あとは普通に実装すればよい。
実装例
#include <iostream>
#include <algorithm>
#include <vector>
using namespace std;
int main() {
int n, t;
cin >> n >> t;
vector<int> c(n), r(n);
for (auto& ci : c) cin >> ci;
for (auto& ri : r) cin >> ri;
if (find(c.begin(), c.end(), t) == c.end()) {
t = c[0];
}
int ans = 0, ma = 0;
for (int i = 0; i < n; i++) {
if (c[i] == t && ma < r[i]) {
ans = i;
ma = r[i];
}
}
cout << ans + 1 << '\n';
}
C - Dango
問題概要
正の整数
に対して、 レベル L のダンゴ文字列とは、以下の条件を満たす文字列です。 L
o
と-
からなる長さの文字列である。 L+1 - 先頭の文字と末尾の文字のうちちょうど一方が
-
であり、そのほかの文字はすべて L o
である。例えば、
ooo-
はレベルのダンゴ文字列ですが、 3 -ooo-
やoo
やo-oo-
などはダンゴ文字列ではありません(より正確には、どのような正の整数に対してもレベル L のダンゴ文字列ではありません) L
種類の文字 2 o-
からなる、長さの文字列 N が与えられます。次の条件を満たすような正整数 S のうち、最大のものを求めてください。 X
の連続する部分文字列であって、レベル S のダンゴ文字列であるものが存在する。 X ただし、そのような整数が存在しない場合、
と出力してください。 -1
-
1\leq N\leq 2\times10^5
解法
要するに、次のような文字列であって最長のものの長さを求めればよい。
-
o
のみからなる -
の連続部分列として現れるS - 前後いずれかに
-
がある
これは、ランレングス圧縮をすることで容易に確認できる。
すこし考察
よくよく考えてみると、o
,-
の両方現れるならば以下が成り立つ:
-
o
のみからなるような部分文字列には、前後のいずれかに-
がついている
これを使うと、単にo
のみからなる部分文字列であって最長のものを求めればよくなる。
実装例
そのまま
#include <iostream>
#include <vector>
using namespace std;
int main() {
int n;
string s;
cin >> n >> s;
vector<pair<char, int>> run_length;
for (int i = 0; i < n;) {
int j = i;
int c = 0;
while (s[i] == s[j]) {
++j;
++c;
}
run_length.emplace_back(s[i], c);
i = j;
}
int x = -1;
for (int i = 0; i < (int)run_length.size(); i++) {
if (run_length[i].first != 'o') continue;
bool is_ok = false;
if (i - 1 >= 0 && run_length[i - 1].first == '-') {
is_ok = true;
}
if (i + 1 < (int)run_length.size() && run_length[i + 1].first == '-') {
is_ok = true;
}
if (is_ok) {
x = max(x, run_length[i].second);
}
}
cout << x << '\n';
}
ちょっと単純にしたやつ
#include <iostream>
#include <algorithm>
#include <vector>
using namespace std;
int main() {
int n;
string s;
cin >> n >> s;
if (find(s.begin(), s.end(), 'o') == s.end() ||
find(s.begin(), s.end(), '-') == s.end()) {
puts("-1");
return 0;
}
int x = 0;
for (int i = 0; i < n;) {
if (s[i] == '-') {
i++;
continue;
}
int len = 0;
while (s[i] == 'o') {
i++, len++;
}
x = max(x, len);
}
cout << x << '\n';
}
D - Find by Query
問題概要
インタラクティブ問題です。
のみからなる長さ 0,1 の文字列 N があり S を満たしています。 S_1=0,S_N=1
あなたは、この文字列に関して以下の形式の質問を S 回まですることができます。 20
? i
:の値を聞く。 S_i
かつ 1\leq p<N を満たすような正整数 S_p\neq S_{p+1} をひとつ出力してください。このような p が存在することは証明できます。 p
-
1\leq N\leq 2\times10^5
解法
以下、1-indexで考えます。
アイデア
制約から、質問回数は
以下のようにすれば良いことが分かる
-
変数
をそれぞれl,r で初期化しておく1,N+1 -
の間、以下を繰り返す。r-l>1 -
として、m=\lfloor{\frac{l+r}{2}}\rfloor の値を聞く。S_m -
ならば、S_m=1 とする。r\leftarrow m -
ならば、S_m=0 とする。l\leftarrow m
-
-
-
を出力l
正当性
変数
これを確認していきましょう。まず、初期状態では明らかに条件を満たしています。
問題は、
として、 m=\lfloor{\frac{l+r}{2}}\rfloor の値を聞く。 S_m
ならば、 S_m=1 とする。 r\leftarrow m ならば、 S_m=0 とする。 l\leftarrow m
のところです。実際に、こうしたとしても条件を満たしていることを確認します。
示すべきは、
{}^\exists p\in[l,m):S_p\neq S_{p+1}\cdots\clubsuit
です。これを背理法で示します。の否定は \clubsuit
{}^\forall p\in[l,m):S_p=S_{p+1}
です。これが成り立つとき、です。 S_l=S_{l+1}=\dots=S_{m}
ところでは l を満たしています。なぜならば S_l=0
- 初期状態では
S_l=S_1=0 - 途中で
と置き換えられる時、 l\leftarrow l' が必要 S_{l'}=0 だからです。
したがって、が成り立たないならば、 \clubsuit です。 S_l=S_{l+1}=\dots=S_{m}=0
しかし、これはという仮定に反します。 S_m=1
したがって、
であることが分かりました。終了時には
実装例
質問回数は
#include <iostream>
using namespace std;
int main() {
int n;
scanf("%d", &n);
fflush(stdout);
int r = n + 1;
int l = 1;
while (r - l > 1) {
int m = (r + l) / 2; // 真ん中を聞いてみる
printf("? %d\n", m);
fflush(stdout);
int res;
scanf("%d", &res);
if (!res) {
l = m;
} else {
r = m;
}
}
printf("! %d\n", l);
fflush(stdout);
}
Discussion