hiro1729 競プロ

競プロの解説などを書きます。

ABC338-E 解説

E - Chords

円周上に $2N$ 点が等間隔に、$1$ 〜 $2N$ の順に番号づけられて並んでいます。$A_i$ と $B_i$ を結ぶ弦で、交わるものはあるかどうか判定してください。

解法では、$A_i < B_i$ になるようにswapした後の説明をします。

解法1 - stackを使う解法

$0 \le i < 2N$ の順番に以下の操作を繰り返します。

  • $A_k = i$ となる $0 \le k < N$ が存在するならstackに $k$ をpushする
  • $B_k = i$ となる $0 \le k < N$ が存在するならstackからpopしてその値を $l$ とおく
    • $k=l$ なら $A_i$ と $B_i$ の間で弦は交わっていない
    • $k \ne l$ なら $A_i$ と $B_i$ で挟まれる部分の内部と外部に跨る弦があるので、交わっている弦がある なのでYesを出力してexitする

全て終わったら、Noを出力します。$O(N)$ です。

from collections import deque
N = int(input())
P = [0] * (2 * N)
for i in range(N):
    A, B = map(int, input().split())
    A -= 1
    B -= 1
    if A > B:
        A, B = B, A
    P[A] = i
    P[B] = N + i
st = deque([])
for i in P:
    if i < N:
        st.append(i)
    else:
        if st.pop() != i - N:
            print("Yes")
            exit()
print("No")
#include <bits/stdc++.h>
using namespace std;

int main() {
    int N;
    cin >> N;
    vector<int> P(2 * N);
    for (int i = 0; i < N; i++) {
        int A, B;
        cin >> A >> B;
        A--; B--;
        if (A > B) {
            swap(A, B);
        }
        P[A] = i;
        P[B] = N + i;
    }
    stack<int> s;
    for (int i: P) {
        if (i < N) {
            s.push(i);
        } else {
            if (s.top() != i - N) {
                cout << "Yes\n";
                return 0;
            }
            s.pop();
        }
    }
    cout << "No\n";
}
import strutils, sequtils

let N = stdin.readLine.parseInt
var P = newSeq[int](2 * N)
for i in 0..<N:
  let a_b = stdin.readLine.split().map(parseInt)
  var
    A = a_b[0] - 1
    B = a_b[1] - 1
  if A > B:
    swap(A, B)
  P[A] = i
  P[B] = N + i
var st = newSeq[int]()
for i in P:
  if i < N:
    st.add(i)
  else:
    if st.pop() != i - N:
      echo "Yes"
      quit()
echo "No"

解法2 - Fenwick Treeを使う解法

はじめに $(A_i, B_i)$ の組でsortしておきます。

$A_i < A_j$ のときに弦 $i$ と $j$ が交わっていないかどうかは、$A_i<A_j<B_i<B_j$ かどうかで判定できます。$i$ に対してこれを満たす個数は、$A_j<A_i$ を満たす $j$ が存在しなければ $A_i<A_j<B_i$ を満たす $j$ の個数から $A_i<B_j<B_i$ を満たす $j$ の個数を引くことで求めることができます。これは、Fenwick Treeで $A_i, B_i$ の区間和を効率的に求めたり各操作の終わりに $A_i, B_i$ で $1$ を引いたりすることができるので $O(N \log(N))$ です。

from atcoder.fenwicktree import FenwickTree

N = int(input())
P = [[0, 0] for _ in range(N)]
fwA = FenwickTree(2 * N)
fwB = FenwickTree(2 * N)
for i in range(N):
    P[i] = list(map(int, input().split()))
    P[i][0] -= 1
    P[i][1] -= 1
    if P[i][0] > P[i][1]:
        P[i][0], P[i][1] = P[i][1], P[i][0]
    fwA.add(P[i][0], 1)
    fwB.add(P[i][1], 1)
P.sort()
for A, B in P:
    if fwB.sum(A + 1, B) < fwA.sum(A + 1, B):
        print("Yes")
        exit()
    fwA.add(A, -1)
    fwB.add(B, -1)
print("No")
#include <bits/stdc++.h>
#include <atcoder/fenwicktree>
using namespace std;
using namespace atcoder;

int main() {
    int N;
    cin >> N;
    vector<pair<int, int>> P(N);
    fenwick_tree<int> fwA(2 * N), fwB(2 * N);
    for (int i = 0; i < N; i++) {
        cin >> P[i].first >> P[i].second;
        P[i].first--;
        P[i].second--;
        if (P[i].first > P[i].second) {
            swap(P[i].first, P[i].second);
        }
        fwA.add(P[i].first, 1);
        fwB.add(P[i].second, 1);
    }
    for (auto [A, B]: P) {
        if (fwB.sum(A + 1, B) < fwA.sum(A + 1, B)) {
            cout << "Yes\n";
            return 0;
        }
        fwA.add(A, -1);
        fwB.add(B, -1);
    }
    cout << "No\n";
}
import strutils, sequtils, algorithm

type BIT = object
  n: int
  a: seq[int]

proc initBIT(n: int): BIT =
  return BIT(n: n, a: newSeq[int](n))

proc add(bit: var BIT, p: int, x: int) =
  var t = p + 1
  while t <= bit.n:
    bit.a[t - 1] += x
    t += t and -t

proc sum0(bit: var BIT, r: int): int =
  var
    s = 0
    x = r
  while x > 0:
    s += bit.a[x - 1]
    x -= x and -x
  return s

proc sum(bit: var BIT, l, r: int): int =
  return bit.sum0(r) - bit.sum0(l)

let N = stdin.readLine.parseInt
var
  P = newSeq[tuple[f: int, s: int]](N)
  fwA = initBIT(2 * N)
  fwB = initBIT(2 * N)
for i in 0..<N:
  let a_b = stdin.readLine.split().map(parseInt)
  P[i].f = a_b[0] - 1
  P[i].s = a_b[1] - 1
  if P[i].f > P[i].s:
    swap(P[i].f, P[i].s)
  fwA.add(P[i].f, 1)
  fwB.add(P[i].s, 1)
sort(P)
for (A, B) in P:
  if fwB.sum(A + 1, B) < fwA.sum(A + 1, B):
    echo "Yes"
    quit()
  fwA.add(A, -1)
  fwB.add(B, -1)
echo "No"

ABC251-D 解説

D - At Most 3 (Contestant ver.)

$10^6$ 未満の正の整数を $100$ 進法で $3$ 桁であらわすことができる。つまり、 $100$ 進法で $100^0$ の位、$100^1$ の位、$100^2$ の位がそれぞれ $0$ 〜 $99$ であるおもりを全て持つと個数は $297$ 個であり、$10^6$ 未満の正の整数を全て良い整数となります。これに $10^6$ のおもりを追加すると個数が $298$ 個かつ $10^6$ 以下の全ての整数が良い整数となるため、条件を満たします。

A = list(range(1, 100)) + list(range(100, 10000, 100)) + list(range(10000, 1010000, 10000))
print(len(A))
print(*A)
#include <bits/stdc++.h>
using namespace std;

int main() {
    vector<int> A;
    for (int i = 1; i < 100; i++) {
        A.push_back(i);
        A.push_back(i * 100);
        A.push_back(i * 10000);
    }
    A.push_back(1000000);
    cout << A.size() << '\n';
    for (int i = 0; i < A.size(); i++) {
        cout << A[i] << ' ';
    }
}

ABC271-E 解説

E - Subsequence Path

DPできます。

長さ$N$のDP配列(これは頂点 $1$ からの最短距離を表します)を $dp[1]=0,dp[i]=inf(i > 1)$ で初期化します。そして、各 $E_i$ に対して、$chmin(dp[A_{E_i}], dp[B_{E_i}] + C_{E_i})$ と遷移していきます。これにより、最短経路が更新されていきます。

inf = 1 << 60

N, M, K = map(int, input().split())
A = [0] * M
B = [0] * M
C = [0] * M
for i in range(M):
    A[i], B[i], C[i] = map(int, input().split())
    A[i] -= 1
    B[i] -= 1
dp = [inf] * N
dp[0] = 0
for E in list(map(int, input().split())):
    E -= 1
    dp[B[E]] = min(dp[B[E]], dp[A[E]] + C[E])
print(dp[N - 1] if dp[N - 1] < inf else -1)
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
const ll inf = 1LL<<60;

int main() {
    int N, M, K, E;
    cin >> N >> M >> K;
    vector<int> A(M), B(M), C(M);
    for (int i = 0; i < M; i++) {
        cin >> A[i] >> B[i] >> C[i];
        A[i]--; B[i]--;
    }
    vector<ll> dp(N, inf);
    dp[0] = 0;
    while (K--) {
        cin >> E; E--;
        dp[B[E]] = min(dp[B[E]], dp[A[E]] + C[E]);
    }
    ll ans = dp[N - 1];
    cout << (ans < inf ? ans : -1) << '\n';
}

ABC078-D 解説

D - ABS

$N=1$ のときは $X$ さんが $1$ 枚取るしかないため答えは $|a_1 - W|$ です。

$2 \le N$ のときは、$X$ さんが最初の操作で全てのカードを取るとスコア $|a_N - W|$ を達成できます。また、最初の操作で $N-1$ 枚のカードを取る、つまり $1$ 枚だけ残すとスコア $|a_{N-1} - a_N|$ を達成できます。$X$ さんが $2$ 枚以上残したとき、$Y$ さんが残り $1$ 枚になるまでカードを取るとスコアは $|a_{N-1} - a_N|$ となります。よって、$X$ さんがカードを $2$ 枚以上残した時、$Y$ さんがスコアを最小化するので、スコアは $|a_{N-1} - a_N|$ 以下になります。$X$ さんはスコアを最大化するので、$2$ 枚以上残す意味はなくなります。よって、答えは $max(|a_N - W|, |a_{N-1} - a_N|)$ です。

N, Z, W = map(int, input().split())
a = list(map(int, input().split()))
if N == 1:
    print(abs(W - a[0]))
else:
    print(max(abs(a[N - 1] - W), abs(a[N - 2] - a[N - 1])))
#include <bits/stdc++.h>
using namespace std;

int main() {
    int N, Z, W;
    cin >> N >> Z >> W;
    vector<int> a(N);
    for (int i = 0; i < N; i++) {
        cin >> a[i];
    }
    if (N == 1) {
        cout << abs(W - a[0]) << '\n';
    } else {
        cout << max(abs(a[N - 1] - W), abs(a[N - 2] - a[N - 1])) << '\n';
    }
}

ABC338-A 解説

A - Capitalized?

問題文の通りに判定します。大文字かどうか、小文字かどうかはそれぞれisupper、islowerなどで判定できます。

S = input()
print("Yes" if S[0].isupper() and all(i.islower() for i in S[1:]) else "No")
#include <bits/stdc++.h>
using namespace std;

int main() {
    string S;
    cin >> S;
    bool ok = true;
    if (!isupper(S[0])) {
        ok = false;
    }
    for (char i: S.substr(1)) {
        if (!islower(i)) {
            ok = false;
        }
    }
    cout << (ok ? "Yes" : "No") << '\n';
}
import strutils, sequtils
let S = stdin.readLine
echo:
  if S[0].isUpperAscii and S[1..<len(S)].allIt(it.isLowerAscii):
    "Yes"
  else:
    "No"

また、言語によっては文字列をcapitalizeする関数もあります。

S = input()
print("Yes" if S.capitalize() == S else "No")
import strutils, unicode
let S = stdin.readLine
echo if S.toLower.capitalize == S: "Yes" else: "No"

istitleは文字列のすべての単語がcapitalizeされているかどうかを判定する関数で、今回の場合はスペースが文字列に含まれていないので使うことができます。

S = input()
print("Yes" if S.istitle() else "No")

また、文字列は比較ができるので、isupperなどを知らなくても解けます。

S = input()
print("Yes" if S[0] <= "Z" and all(i >= "a" for i in S[1:]) else "No")
#include <bits/stdc++.h>
using namespace std;

int main() {
    string S;
    cin >> S;
    bool ok = true;
    if (S[0] > 'Z') {
        ok = false;
    }
    for (char i: S.substr(1)) {
        if (i < 'a') {
            ok = false;
        }
    }
    cout << (ok ? "Yes" : "No") << '\n';
}
import strutils, sequtils
let S = stdin.readLine
echo:
  if S[0] <= 'Z' and S[1..<len(S)].allIt(it >= 'a'):
    "Yes"
  else:
    "No"

ABC337-C 解説

C - Lining Up 2

解法1

人 $i$ に対してその後ろにいる人 $B_i$ を求める方法です。$B$ を求める時に $A_i=-1$ となる $i$ に対しては列の先頭なのでそれを持つ必要もあります。

N = int(input())
A = list(map(int, input().split()))
B = [-1] * N
now = 0
for i in range(N):
    if A[i] == -1:
        now = i
    else:
        B[A[i] - 1] = i
ans = [now + 1]
for i in range(N - 1):
    now = B[now]
    ans.append(now + 1)
print(*ans)
#include <bits/stdc++.h>
using namespace std;

int main() {
    int N;
    cin >> N;
    vector<int> A(N);
    for (int i = 0; i < N; i++) {
        cin >> A[i];
    }
    vector<int> B(N, -1);
    int now = 0;
    for (int i = 0; i < N; i++) {
        if (A[i] == -1) {
            now = i;
        } else {
            B[A[i] - 1] = i;
        }
    }
    vector<int> ans = {now + 1};
    for (int i = 0; i < N - 1; i++) {
        now = B[now];
        ans.push_back(now + 1);
    }
    for (int i = 0; i < N; i++) {
        cout << ans[i] << ' ';
    }
}

解法2

列を後ろから決定していく方法です。最初に $A$ に現れない値を求めてさかのぼっていきます。最後に逆順に出力することに注意してください。

N = int(input())
A = list(map(int, input().split()))
cnt = [0] * N
for i in A:
    if i != -1:
        cnt[i - 1] = 1
now = -1
for i in range(N):
    if cnt[i] == 0:
        now = i
ans = [now + 1]
for _ in range(N - 1):
    now = A[now] - 1
    ans.append(now + 1)
print(*ans[::-1])
#include <bits/stdc++.h>
using namespace std;

int main() {
    int N;
    cin >> N;
    vector<int> A(N);
    for (int i = 0; i < N; i++) {
        cin >> A[i];
    }
    vector<int> cnt(N);
    for (int i: A) {
        if (i != -1) {
            cnt[i - 1] = 1;
        }
    }
    int now = -1;
    for (int i = 0; i < N; i++) {
        if (cnt[i] == 0) {
            now = i;
        }
    }
    vector<int> ans = {now + 1};
    for (int i = 0; i < N - 1; i++) {
        now = A[now] - 1;
        ans.push_back(now + 1);
    }
    for (int i = N - 1; i >= 0; i--) {
        cout << ans[i] << ' ';
    }
}

ABC337-B 解説

B - Extended ABC

解法1 隣接する部分だけ見る方法

$S$ の隣接する2文字を見ます。これがAA,AB,AC,BB,BC,CCのいずれかならば$S$は拡張ABC文字列です。逆に言うと、BA,CA,CBのいずれかが含まれていると$S$は拡張ABC文字列ではありません。

S = input()
for i in range(len(S) - 1):
    if S[i: i + 2] in ["BA", "CA", "CB"]:
        print("No")
        exit()
print("Yes")
#include <bits/stdc++.h>
using namespace std;

int main() {
    string S;
    cin >> S;
    for (int i = 0; i < S.size() - 1; i++) {
        if ((S[i] == 'B' && S[i + 1] == 'A') || (S[i] == 'C' && (S[i + 1] == 'A' || S[i + 1] == 'B'))) {
            cout << "No\n";
            return 0;
        }
    }
    cout << "Yes\n";
}

解法2

解法1をよく見つめてみましょう。BA,CA,CBは、全て1文字目が0文字目よりも小さくなっています。逆に言うと、全ての長さ2の部分列に対して1文字目が0文字目以上であるかどうかを判定すれば解けます。

S = input()
print("Yes" if all(S[i] <= S[i + 1] for i in range(len(S) - 1)) else "No")
#include <bits/stdc++.h>
using namespace std;

int main() {
    string S;
    cin >> S;
    bool ok = true;
    for (int i = 0; i < S.size() - 1; i++) {
        if (S[i] > S[i + 1]) ok = false;
    }
    cout << (ok ? "Yes\n" : "No\n");
}

解法3

解法2をよく見つめてみましょう。全ての長さ2の部分列に対して1文字目が0文字目以上であることは、ソートされていることと同値です。

S = input()
print("Yes" if list(S) == list(sorted(S)) else "No")
#include <bits/stdc++.h>
using namespace std;

int main() {
    string S;
    cin >> S;
    string T = S;
    sort(T.begin(), T.end());
    cout << (S == T ? "Yes\n" : "No\n");
}

解法4 ランレングス圧縮する

ランレングス圧縮とは、同じ文字が続いている部分をまとめる圧縮方法です。今回は、まとめた部分の文字数は必要ないので、文字列だけ持ちます。ただし、C++にはuniqueという重複削除の関数があるのでこれを使うと簡単です。

S = input()
T = ""
i = 0
while i < len(S):
    j = i
    while j < len(S) and S[i] == S[j]:
        j += 1
    T += S[i]
    i = j
print("Yes" if T == "AC" or T in "ABC" else "No")
#include <bits/stdc++.h>
using namespace std;

int main() {
    string S;
    cin >> S;
    S.erase(unique(S.begin(), S.end()), S.end());
    for (string T: {"A", "B", "C", "AB", "AC", "BC", "ABC"}) {
        if (S == T) {
            cout << "Yes\n";
            return 0;
        }
    }
    cout << "No\n";
}

解法5 最初から見ていく

最初$i=0$として、$S_i=$Aである間$i$を進め、$S_i=$Bである間$i$を進め、$S_i=$Cである間$i$を進めて、$i=|S|$になれば$S$は拡張ABC文字列です。

S = input()
i = 0
while i < len(S) and S[i] == 'A':
    i += 1
while i < len(S) and S[i] == 'B':
    i += 1
while i < len(S) and S[i] == 'C':
    i += 1
print("Yes" if i == len(S) else "No")
#include <bits/stdc++.h>
using namespace std;

int main() {
    string S;
    cin >> S;
    int i = 0;
    while (i < S.size() && S[i] == 'A') {
        i++;
    }
    while (i < S.size() && S[i] == 'B') {
        i++;
    }
    while (i < S.size() && S[i] == 'C') {
        i++;
    }
    cout << (i == S.size() ? "Yes\n" : "No\n");
}

解法6 正規表現を使う解法

拡張ABC文字列は、正規表現ABC*にマッチします。これを判定すれば解けます。

import re
print("Yes" if re.search(r"^A*B*C*$", input()) else "No")
#include <bits/stdc++.h>
using namespace std;

int main() {
    string S;
    cin >> S;
    regex reg{R"(^A*B*C*$)"};
    smatch m;
    cout << (regex_search(S, m, reg) ? "Yes\n" : "No\n");
}

Sed

/^A*B*C*$/cYes
cNo