−目次
AtCoder Beginner Contest 158 D,E,F問題メモ
D - String Formation
問題
- 英小文字から成る文字列 S がある
- この S に Q 回の操作を加えて新しい文字列を作る
- i 回目の操作は、以下の2種類からなる
1:- 文字列 S を反転する
2 Fi Ci:- Fi=1 のとき、S の先頭に英小文字 Ci を追加する
- Fi=2 のとき、S の末尾に英小文字 Ci を追加する
- 最終的にできる文字列を答えよ
- 1≤|S|≤105
- 1≤Q≤2×105
解法
だいたいその通りにやるだけ、なんだけど、文字列の反転操作や、末尾以外への追加は重たいので、それをなるべく避けたコードを書く。
「今、反転してるか?」をフラグで持ち、実際に反転はしないで処理を分岐させるとよい。
文字列処理のパフォーマンスは、わりと言語に依るところも大きいのかも。Pythonでは、(細かなケースは分からないが、概して)PyPyよりPythonの方が速い。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
import syss = input()q = int(input())rev = 0append_front_rev = ''append_rear = ''for line in sys.stdin: q = line.strip() if q == '1': rev ^= 1 else: _, f, c = q.split() if f == '1': if rev == 0: append_front_rev += c else: append_rear += c else: if rev == 0: append_rear += c else: append_front_rev += cs = append_front_rev[::-1] + s + append_rearif rev: s = s[::-1]print(s) |
E - Divisible Substring
問題
'0'~'9'までの数字からなる長さ N の文字列 S がある- S から連続した部分文字列を切り出す方法は N(N+1)2 通りある
- そのうち素数 P の倍数となっているものの個数を求めよ
- 1≤N≤2×105
- 2≤P<10000
解法
桁DP……っぽさはあるけど、方向が大事。下から計算するとわかりやすい。
i 桁目以降を10進表記の数字と見なしたものを、Ti とする。
i 0 1 2 3 4 S 1 3 0 0 3 T1 3 0 0 3 T3 0 3
これを使って部分文字列 S[l,r) を数式で表すと、Tl−Tr10r−l となる。これが P の倍数となるには、どのような条件が必要か?
10=2×5 なので、P がこの2つの素数の場合はちょっと場合分けが必要だが、いずれも簡単に判定できる。
P=2の場合
末尾が偶数なら2の倍数である。i 番目に偶数が出てきたら、そこを右端とする部分文字列は i+1 個作れる。
i 0 1 2 3 4 S 1 3 0 0 3 [ [ [ ] i=2を右端とする部分文字列は、130, 30, 0 の3つ作れる
よって、S を先頭から見ていって、偶数ならその位置に応じた部分文字列数を適宜加えていけばよい。
P=5の場合
末尾が0か5なら5の倍数である。あとは P=2 と同じ。
その他の場合
Tl−Tr10r−l が P の倍数であるためには、10r−l と素数 P は互いに素なので、 Tl−Tr が P の倍数であればよい。
つまり、下から TimodP を計算しておき、値が等しくなる2箇所を l,r とすると、上手くいく。
P=7
i 0 1 2 3 4 (5)
-------------------------
S 1 3 0 0 3
Ti % P 4 0 3 3 3 0 (末尾に便宜的に0を追加)
[l, r) = [1, 5) 3003
[2, 3) 0
[2, 4) 00
[3, 4) 0
この4つが P=7 の倍数となっている
個数を数えるには、全ての TimodP を計算した後、各数字が何回出てくるかをカウントする。
ある数字が c 回出てきたら、その数字の位置から2箇所を選ぶ組み合わせは c(c−1)2 個あるので、これを足し合わせればよい。
4: 1回 0個 0: 2回 → 2 * (2-1) / 2 = 1個 3: 3回 → 3 * (3-1) / 2 = 3個 → 合計4個
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 |
from collections import defaultdictdef solve2(s): ans = 0 for i, c in enumerate(s): if c in '02468': ans += i + 1 return ansdef solve5(s): ans = 0 for i, c in enumerate(s): if c in '05': ans += i + 1 return ansdef solve_other(s, p): reminders = defaultdict(lambda: 0) tmp = 0 mul = 1 for c in s[::-1]: c = int(c) tmp = (tmp + c * mul) % p mul = mul * 10 % p reminders[tmp] += 1 reminders[0] += 1 ans = 0 for r, cnt in reminders.items(): ans += cnt * (cnt - 1) // 2 return ansn, p = list(map(int, input().split()))s = input()if p == 2: print(solve2(s))elif p == 5: print(solve5(s))else: print(solve_other(s, p)) |
F - Removing Robots
問題
- 数直線上に N 個のロボットがある
- ロボット i ははじめ座標 Xi に位置する
- 起動すると Di だけ正の方向に移動し、移動を終えると消滅する
- 移動途中に他の停止中のロボットと同じ座標に来ると、そのロボットを起動する(移動は継続する)
- 起動されたロボットも同じように移動を開始し、他のロボットと同じ座標に来るとそいつを起動する
- ちょうど移動終了で接触する(Xi+Di=Xj)時、ロボット i はロボット j を起動しない
- 今、以下の操作を好きなだけ繰り返す(1回も行わないことも可能)
- ロボットを1つ起動する
- 移動中のロボットがいなくなるまで待つ
- 操作を繰り返した後、数直線上に残るロボットの組み合わせとして考えられるパターン数を mod998244353 で求めよ
- 1≤N≤2×105
- −109≤Xi≤109
- 1≤Di≤109
解法
「ロボット i を起動したら、右側に連続するここまでのロボットが連鎖的に起動され、消滅する」というのが各ロボットにつきある。
言い換えると、連鎖的に起動する範囲内のロボットを残したまま、ロボット i を消滅させる、ということはできない。
(例)ロボットの初期位置と影響範囲
1 2 3 4 5 6 7 8 9
|-----------| |-| |---| |-|
|-| |-|
|--------|
|-|
|---|
ロボット1は、起動すると2,3,4を起動し、さらに3が5を起動するため、2~5も連鎖的に消滅する。つまり、1を消滅させつつ2とか5を残すことはできない。
1から右のロボットのみに着目した場合の数は、「1を残した場合の数」+「1を起動させた場合の数」となるので、この2つに分けて考える。
- 1を残した場合の数
- そのまま「2から右のロボットのみに着目した場合の数」となる
- 1を起動させた場合の数
- 1を起動させることで消滅しない、最も左のロボットを k(例では6)とすると、「k から右のロボットのみに着目した場合の数」となる
よって、以下のDPを定義し、右から順に埋めていくとよい。
- DP[i]=i から右のロボットのみに注目したときの場合の数
ここで、ロボット i を起動したら、どこまでのロボットが消滅するのか?を、ある程度高速に求める必要がある。
直接的に起動するロボットは X を昇順に並べ Xi+Di を二分探索するなどで求められるが、 間接的に起動したロボットを含めるとどこまで行くか? はなかなか難しそうに見える。
例では、1が直接的に起動するのは4までだが、実際は5まで起動される。 しかし5を起動させるのは3であり、それを調べるには、端の4だけを調べるのでは不足で、2~4を全て調べて最大値を得なければならない。
実装するには、セグメント木で「ロボット i を起動すると、ロボット pi まで消滅する」 という情報を持てばよい。
自身が直接的に起動するロボット番号の右端 j を二分探索で見つけ、pi+1~pj の中で最大の pk を見つける。 DP[pk+1] が、i を起動した場合に DP[i] に加算される場合の数となる。 1点更新、区間Max取得のセグメント木で実装可能。
計算量は、O(NlogN) となる。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 |
import sysfrom bisect import bisect_leftfrom operator import itemgetterclass SegTreeMax: """ 以下のクエリを処理する 1.update: i番目の値をxに更新する 2.get_min: 区間[l, r)の最小値を得る """ def __init__(self, n, INF): """ :param n: 要素数 :param INF: 初期値(入りうる要素より十分に大きな数) """ n2 = 1 << (n - 1).bit_length() self.offset = n2 self.tree = [INF] * (n2 << 1) self.INF = INF def initialize(self, arr): for i, v in enumerate(arr, start=self.offset): self.tree[i] = v for i in range(self.offset - 1, 0, -1): j = i << 1 self.tree[i] = max(self.tree[j], self.tree[j + 1]) def update(self, i, x): """ i番目の値をxに更新 :param i: index(0-indexed) :param x: update value """ i += self.offset self.tree[i] = x while i > 1: y = self.tree[i ^ 1] if y >= x: break i >>= 1 self.tree[i] = x def get_max(self, a, b): """ [a, b)の最大値を得る :param a: index(0-indexed) :param b: index(0-indexed) """ result = self.INF l = a + self.offset r = b + self.offset while l < r: if r & 1: result = max(result, self.tree[r - 1]) if l & 1: result = max(result, self.tree[l]) l += 1 l >>= 1 r >>= 1 return resultn, *xd = map(int, sys.stdin.buffer.read().split())MOD = 998244353xd = sorted(zip(xd[0::2], xd[1::2]))xxx = list(map(itemgetter(0), xd))ans = [0] * (n + 1)skip = list(range(1, n + 1))ans[n] = 1sgt = SegTreeMax(n + 1, 0)sgt.initialize(list(range(1, n + 2)))for i in range(n - 1, -1, -1): x, d = xd[i] r = x + d j = bisect_left(xxx, r) k = sgt.get_max(i, j) ans[i] = (ans[i + 1] + ans[k]) % MOD sgt.update(i, k)print(ans[0]) |
スタックで管理してもよい。
スタックには、右から処理して i まで処理した上で、「現時点で他のどのロボットによっても起動されることはない」ロボットの、座標と番号を積んでいく。
1 2 3 4 5 6 7 8 9
|-----------| |-| |---| |-|
|-| |-|
|--------|
|-|
|---|
4まで処理したら、スタックは [9,7,6,5,4] となっている。
次、3を処理するとき、X3+D3 より Xi が小さいものは、末尾からpopしていく。
そうして残ったスタックの末尾要素(6)が、3を起動したときに残る左端のロボット番号となる。
そして自身(3)を積んで、次のロボットに進む。この時点でスタックは [9,7,6,3] となる。
セグメント木より問題特化な解法だが、こちらの方が高速で計算量は O(N) となる。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
import sysfrom operator import itemgettern, *xd = map(int, sys.stdin.buffer.read().split())MOD = 998244353xd = sorted(zip(xd[0::2], xd[1::2]))xxx = list(map(itemgetter(0), xd)) + [10 ** 10]ans = [0] * n + [1]stack = [n]for i in range(n - 1, -1, -1): x, d = xd[i] r = x + d while r > xxx[stack[-1]]: stack.pop() ans[i] = (ans[i + 1] + ans[stack[-1]]) % MOD stack.append(i)print(ans[0]) |

