TechFUL: マスクを買いたい[easy]

問題概要

Tech ちゃんは 24 時間営業の店に行く.

時刻  S から時刻  T 直前まで、お客さんが多いことが分かっている.

Tech ちゃんはお客さんが少ない時間を狙って買い物にいく.

Tech ちゃんが買い物に向かうべき時間を出力せよ.

制約

  •  0 \le S \le T \le 24

  • Tech ちゃんは必ず買い物に行く.

解法

 S \le x \lt  T となる時間  x に行かないようにする.

但し、例外として  S = 0, T = 24 の場合があり、全ての時間に行くことができる.

#include <bits/stdc++.h>
using namespace std;

int main() {
    int S, T;
    cin >> S >> T;
    if (S == 0 && T == 24) {
        for (int i = 0; i < 24; ++i) {
            cout << i << " ";
        }
        cout << endl;
    } else {
        for (int i = 0; i < 24; ++i) {
            if (S <= i && i < T) continue;
            cout << i << " ";
        }
        cout << endl;
    }
}

TechFUL: Polygon Cutting

問題概要

 N 角形の紙が 1 枚ある.

Tech ちゃんは以下の操作によって紙をいくつかの多角形に切り分ける.

  • 多角形  P の任意の対角線に沿って紙を切り、2 つの多角形  Q, R に切り分ける.

この操作を繰り返したとき、最大で何個の多角形に切り分けることが出来るか求めよ.

制約

  •  4 \leq N \leq 10^{6}

解法

操作の回数の最大値は 1 つの頂点から引ける対角線の本数と等しく  N - 3 回である.

1 回の操作で多角形は 1 つ増えるので、最終的な多角形の個数は  (N - 3) + 1 = N - 2 個である.

#include <bits/stdc++.h>
using namespace std;

int main() {
    int N;
    cin >> N;
    cout << N - 2 << endl;
}

TechFUL: エレベーター

問題概要

Tech ちゃんが乗るエレベーターは 1 階上がることに、 N 秒間かかる.

Tech ちゃんがエレベーターに乗り、 S 秒間経ったときのエレベーターの現在の階数を求めよ.

但し、Tech ちゃんはエレベーターを 1 階から乗り、エレベーターの階に上限はない.

制約

  •  1 \leq N \leq 10^{3}

  •  1 \leq S \leq 10^{3}

解法

階数は  \left\lfloor \frac{S}{N}\right\rfloor + 1 である.

#include <bits/stdc++.h>
using namespace std;

int main() {
    int N, S;
    cin >> N >> S;
    cout << S / N + 1 << endl;
}

TechFUL: Improving Binary

問題概要

バイナリ文字列  S が与えられる.

文字列の良さとは、1 が連続して並んでいる区間の長さの最大値である.

以下の操作を  K 回まで行える.

  •  S の文字が 0 である箇所を 1 つ選び、1 に変える.

適切に操作したときの文字列の良さの最大値を求めよ.

問題のリンク

制約

  •  S は 0 または 1 からなる文字列

  •  1 \leq | S | \leq 10^{5}

  •  0 \leq K \leq (S に含まれる 0 の個数)

解法1: 二分探索

 0 \leq i \lt | S | について、位置  i を始点に右に 1 ができるだけ長く連なるように操作を行う.

最初に文字 0 が現れる位置を  r_{i} とする.

このとき、 \displaystyle \max_{0 \leq i \lt | S |} r_{i} - i が答えである.

例として、S = 001001011, K = 2 の場合をみると

  • 位置 0 を始点とした場合、S = 001001011 の位置 0, 1 の 0 を 1 に変えれば、
    S = 111001011 となり、 r_{0} = 3 となる.

  • 位置 1 を始点とした場合、S = 001001011 の位置 1, 3 の 0 を 1 に変えれば、
    S = 011101011 となり、 r_{1} = 4 となる.

  • 位置 2 を始点とした場合、S = 001001011 の位置 3, 4 の 0 を 1 に変えれば、
    S = 001111011 となり、 r_{2} = 6 となる.

次に  r_{i} の計算であるが、 これは  S_{0} ... S_{j} に文字 0 がいくつ存在するか数える配列  cnt_{j} を事前に計算しておけば、 二分探索を使用して  O(\log | S |) 時間でできる.

#include <bits/stdc++.h>
using namespace std;

string S;
int K;
int cnt[100010];

int main() {
    cin >> S >> K;

    for (int i = 0; i < S.size(); ++i) {
        cnt[i + 1] = cnt[i] + (S[i] == '0');
    }

    int ans = 0;
    for (int i = 0; i < S.size(); ++i) {
        int ok = i, ng = S.size() + 1;
        while (ng - ok > 1) {
            int m = (ok + ng) / 2;
            if (cnt[m] - cnt[i] <= K) ok = m;
            else ng = m;
        }
        ans = max(ans, ok - i);
    }
    cout << ans << endl;
}

解法2: しゃくとり法

上の  r_{i} の値は  1 \leq i \lt | S | において  r_{i-1} \leq r_{i} が成り立つ.

そのため、 r_{i} はしゃくとり法を使って全体で  O(| S |) 時間で計算できる.

#include <bits/stdc++.h>
using namespace std;

string S;
int K;
int cnt[100010];

int main() {
    cin >> S >> K;

    for (int i = 0; i < S.size(); ++i) {
        cnt[i + 1] = cnt[i] + (S[i] == '0');
    }

    int ans = 0, j = 0;
    for (int i = 0; i < S.size(); ++i) {
        while (j <= S.size() && cnt[j] - cnt[i] <= K) {
            j++;
        }
        ans = max(ans, j - 1 - i);
    }
    cout << ans << endl;
}

TechFUL: Xor Rush

ABC147 の Xor Sum 4 と実質同じ問題.

問題概要

長さ  N の整数列  A と長さ  M の整数列  B が与えられる.

 \displaystyle
\sum_{i=0}^{N-1} \sum_{j=0}^{M-1} A_{i} \oplus B_{j}
\tag{1}
を求めよ.

問題のリンク

制約

  •  1 \leq N \leq 2 \times 10^{5}

  •  0 \leq A_{i}, B_{j} \leq 10^{6}

解法

XOR 系の問題なので  A_{i}, B_{j} を2進数表示して、各桁ごとに考えてみる.

まず、 A_{i} \oplus B_{j} を2進数表示すると何桁になるか調べる.

これは、制約から  0 \leq A_{i}, B_{j} \leq 10^{6} より  A_{i} \oplus B_{j} \leq 10^{6} \lt 2^{20}

となり、高々20桁と分かる.

次に、式 (1) を2進数表示を意識して式変形する.


\begin{align}
\sum_{i=0}^{N-1} \sum_{j=0}^{M-1} A_{i} \oplus B_{j}
&= \sum_{i=0}^{N-1} \sum_{j=0}^{M-1} \sum_{d=0}^{19} \lbrace (A_{i} の d 桁目) \oplus (B_{j} の d 桁目) \rbrace \times 2^{d} \\
&= \sum_{d=0}^{19} 2^{d} \sum_{i=0}^{N-1} \sum_{j=0}^{M-1}  \lbrace (A_{i} の d 桁目) \oplus (B_{j} の d 桁目) \rbrace \\
&= \sum_{d=0}^{19} 2^{d} \left(cntA_{d, 0} \cdot cntB_{d, 1} + cntA_{d, 1} \cdot cntB_{d, 0} \right)
\end{align}
\tag{2}

ただし、 cntA_{d, b} d ビット目が  b \in \lbrace 0, 1\rbrace である  A の要素の個数とする.

同様に  cntB_{d, b} も定義する.

以上より、 cntA, cntB を計算すれば、式 (2) を基に答えを得る.

#include <bits/stdc++.h>
using namespace std;

int main() {
    int N, M;
    cin >> N >> M;
    vector<int> A(N), B(M);
    for (int i = 0; i < N; ++i) cin >> A[i];
    for (int i = 0; i < M; ++i) cin >> B[i];

    long long cntA[20][2] = {}, cntB[20][2] = {};
    for (int d = 0; d < 20; ++d) {
        for (int i = 0; i < N; ++i) {
            cntA[d][A[i] >> d & 1]++;
        }
        for (int i = 0; i < M; ++i) {
            cntB[d][B[i] >> d & 1]++;
        }
    }

    long long ans = 0;
    for (int d = 0; d < 20; ++d) {
        ans += (1LL << d) * (cntA[d][0] * cntB[d][1] + cntA[d][1] * cntB[d][0]);
    }
    cout << ans << endl;
}

TechFUL: Techちゃんとバウムクーヘン泥棒【easy】

問題概要

Techちゃんは好みの男の子にバウムクーヘンを作りたい.

Techちゃんは  x 個入りのバウムクーヘンを好きな個数だけ自由に作る.

しかし、バウムクーヘンの個数が  m 個以上のとき、 m 個未満になるまでカラスの集団が  m 個ずつ食べてしまう.

男の子は  r \; (\lt m) 個のバウムクーヘンを貰うと Tech ちゃんを好きになる.

男の子が Tech ちゃんを好きにさせることが可能か判定せよ.

問題のリンク

制約

  •  1 \leq x, m \leq 10^{18}
  •  0 \leq r \lt m

解法

Tech ちゃんが  xp \; (p \geq 0) 個のバウムクーヘンを作り、

カラスが  mq \; (q \geq 0)バウムクーヘンを食べたとする.

このとき、 xp = mq + r が成り立つ.

これを式変形すると、 xp - mq = r という一次不定方程式になる.

この方程式が整数解を持つための必要十分条件は r が gcd(x, m) の倍数を満たすことである.

一次不定方程式については以下の drken さんの記事が参考になる.

qiita.com

この方程式の整数解がどちらも 0 以上になるかを考える.

 p, q xp - mq = r を満たす整数解の一つとする.

また、 p', q' xp' = mq' となる整数とすると、 x, m > 0 より  p', q' は同符号である.

 x(p+p') - m(q+q') = r となるため、 p+p', q+q' も一次不定方程式の整数解となる.

 p', q' を十分大きな値にすれば、 p+p', q+q' はどちらも  0 以上の整数にすることができる.

よって、 xp - mq = r には  0 以上の整数解  p, q が存在する.

#include <bits/stdc++.h>
using namespace std;

int main() {
    long long x, m, r;
    cin >> x >> m >> r;
    cout << (r % gcd(x, m) == 0 ? "Yes" : "No") << endl;
}

TechFUL: スーパー転倒数

問題概要

長さ  N の順列  A, B が与えられる.

次の 2 つの条件を満たす  (i, j) の組の個数を求めよ.

  •  A \lbrack i \rbrack \gt A \lbrack j \rbrack
  •  B \lbrack i \rbrack \lt B \lbrack j \rbrack

問題のリンク

制約

  •  1 \lt N \leq 50000
  •  1 \lt A \lbrack i \rbrack, B \lbrack i \rbrack \leq N
  •  A, B は長さ  N の順列

解法

数列の転倒数は、その数列をマージソートする過程で計算する.

そのため、スーパー転倒数も数列をマージソートする過程で計算できないか考えてみる.

マージソートの実装や性質については drken さんの記事が参考になる.

qiita.com

スーパー転倒数の計算

 A, B をそれぞれ2つの数列  A_{1}, A_{2}, B_{1}, B_{2} に分割する.

数列  A, B のスーパー転倒数には次の3種類のスーパー転倒が起こる.

  •  A_{1}, B_{1} の要素のみで起こるスーパー転倒

  •  A_{2}, B_{2} の要素のみで起こるスーパー転倒

  •  A_{1}, B_{1} A_{2}, B_{2} の要素間で起こるスーパー転倒

1つ目ケースは、 A_{1}, B_{1} を再び分割して再帰的に計算することができる (2つ目のケースも同様).

3つ目のケースを考える.  A_{1}, A_{2} それぞれをソートした数列を  A_{1}', A_{2}' とする. また、 B_{1}, B_{2} A_{1}', A_{2}' の順に対応するように並び替えた数列を  B_{1}', B_{2}' とする.  A_{1}, B_{1} A_{2}, B_{2} の要素間で起こる転倒数は  A_{1}' \lbrack i \rbrack \gt A_{2}' \lbrack j \rbrack かつ  B_{1}' \lbrack i \rbrack \lt B_{2}' \lbrack j \rbrack となる  (i, j) の組の個数に等しい.

これは、次のようなアルゴリズムで計算できる.

  1.  i = 0, j = 0, num = 0, v = \min\lbrace A_{1}'\lbrack i\rbrack, A_{2}'\lbrack j\rbrack \rbrace に設定する.
     A_{1}' \lbrack |A_{1}'| \rbrack = \infty, A_{2}' \lbrack |A_{2}'| \rbrack = \infty とする.
     i = |A_{1}'|, j = |A_{2}'| になったら 5. に移動する.
  2.  v = A_{1}\lbrack i\rbrack のとき、
     i 1 を追加して 2. に戻る.
  3.  v = A_{2}\lbrack j\rbrack' のとき、
     num B_{1}' \lbrack i \rbrack, B_{1}' \lbrack i+1 \rbrack, \ldots, B_{1}' \lbrack |B_{1}'|-1 \rbrack の中で  B_{2}' \lbrack j \rbrack より小さい要素の個数を追加する.
     j 1 を追加して 2. に戻る.
  4.  num A_{1}', B_{1}' A_{2}', B_{2}' の要素間で起こるスーパー転倒数になっている.

操作 4. の  B_{1}' \lbrack i \rbrack, B_{1}' \lbrack i+1 \rbrack, \ldots, B_{1}' \lbrack |B_{1}'|-1 \rbrack B_{2}' \lbrack j \rbrack より小さい要素の個数の計算は BIT や セグ木を使用して  O(log |B_{1}'|) 時間で計算できる.

計算例

例として、本問のサンプルケース2を扱う.  A = (7, 4, 3, 2, 6, 1, 5), B = (2, 1, 4, 3, 6, 5, 7) である.

 A, B をそれぞれ2つの数列  A_{1} = (7, 4, 3), A_{2} = (2, 6, 1, 5), B_{1} = (2, 1, 4), B_{2} = (3, 6, 5, 7) に分割する.

先程の例で確認してみる. A_{1}' = (3, 4, 7), B_{1}' = (4, 1, 2), A_{2}' = (1, 2, 6, 5), B_{2}' = (5, 3, 7, 6) である.

  •  i = 0, j = 0, num = 0

  •  A_{1}' \lbrack i \rbrack = A_{1}' \lbrack 0 \rbrack = 3, A_{2}' \lbrack j \rbrack = A_{2}' \lbrack 0 \rbrack = 1 より  A_{1}' \lbrack i \rbrack \gt A_{2}' \lbrack j \rbrack

    •  i = 0
    •  B_{1}' \lbrack i:2 \rbrack = B_{1}' \lbrack 0:2 \rbrack = (4, 1, 2) B_{2}' \lbrack j \rbrack = B_{2}' \lbrack 0 \rbrack = 5 より小さい要素の個数は 3 より、 num = 0 + 3 = 3
    •  j = 1
  •  A_{1}' \lbrack i \rbrack = A_{1}' \lbrack 0 \rbrack = 3, A_{2}' \lbrack j \rbrack = A_{2}' \lbrack 1 \rbrack = 2 より  A_{1}' \lbrack i \rbrack \gt A_{2}' \lbrack j \rbrack

    •  i = 0
    •  B_{1}' \lbrack i:2 \rbrack = B_{1}' \lbrack 0:2 \rbrack = (4, 1, 2) B_{2}' \lbrack j \rbrack = B_{2}' \lbrack 1 \rbrack = 3 より小さい要素の個数は 2 より、 num = 3 + 2 = 5
    •  j = 2
  •  A_{1}' \lbrack i \rbrack = A_{1}' \lbrack 0 \rbrack = 3, A_{2}' \lbrack j \rbrack = A_{2}' \lbrack 2 \rbrack = 6 より  A_{1}' \lbrack i \rbrack \lt A_{2}' \lbrack j \rbrack

    •  i = 1, j = 2, num = 5
  •  A_{1}' \lbrack i \rbrack = A_{1}' \lbrack 1 \rbrack = 4, A_{2}' \lbrack j \rbrack = A_{2}' \lbrack 2 \rbrack = 6 より  A_{1}' \lbrack i \rbrack \lt A_{2}' \lbrack j \rbrack

    •  i = 2, j = 2, num = 5
  •  A_{1}' \lbrack i \rbrack = A_{1}' \lbrack 2 \rbrack = 7, A_{2}' \lbrack j \rbrack = A_{2}' \lbrack 2 \rbrack = 6 より  A_{1}' \lbrack i \rbrack \gt A_{2}' \lbrack j \rbrack

    •  i = 2
    •  B_{1}' \lbrack i:2 \rbrack = B_{1}' \lbrack 2:2 \rbrack = (2) B_{2}' \lbrack j \rbrack = B_{2}' \lbrack 2 \rbrack = 7 より小さい要素の個数は 1 より、 num = 5 + 1 = 6
    •  j = 3
  •  A_{1}' \lbrack i \rbrack = A_{1}' \lbrack 2 \rbrack = 7, A_{2}' \lbrack j \rbrack = A_{2}' \lbrack 3 \rbrack = 5 より  A_{1}' \lbrack i \rbrack \gt A_{2}' \lbrack j \rbrack

    •  i = 2
    •  B_{1}' \lbrack i:2 \rbrack = B_{1}' \lbrack 2:2 \rbrack = (2) B_{2}' \lbrack j \rbrack = B_{2}' \lbrack 3 \rbrack = 6 より小さい要素の個数は 1 より、 num = 6 + 1 = 7
    •  j = 4
  •  A_{1}' \lbrack i \rbrack = A_{1}' \lbrack 2 \rbrack = 7, A_{2}' \lbrack j \rbrack = A_{2}' \lbrack 4 \rbrack = ∞ より  A_{1}' \lbrack i \rbrack \lt A_{2}' \lbrack j \rbrack

    •  i = 3, j = 4, num = 7
  •  i = 3, j = 4 より終了

以上より、 A_{1}', B_{1}' A_{2}', B_{2}' の要素間のスーパー転倒数は 7 となる.

これに、 A_{1}', B_{1}' の要素のみのスーパー転倒数と  A_{2}', B_{2}' の要素のみのスーパー転倒数を加えることで、 A, B のスーパー転倒数が求まる.

このアルゴリズム O(N \log^{2} N) 時間で計算できる.

#include <bits/stdc++.h>
using namespace std;

// Binary Indexed Tree
template <class T> struct BIT {
    vector<T> d;

    BIT(int n) { init(n); }
    void init(int n) { d.assign(n + 1, 0); }

    // a: 1-indexed
    inline void add(int a, T x) {
        for (int i = a; i < (int)d.size(); i += i & -i) {
            d[i] = d[i] + x;
        }
    }

    // [1, a]
    // a: 1-indexed
    inline T sum(int a) {
        T res = 0;
        for (int i = a; i > 0; i -= i & -i) {
            res = res + d[i];
        }
        return res;
    }

    // [a, b)
    // a, b: 1-indexed
    inline T sum(int a, int b) {
        return sum(b - 1) - sum(a - 1);
    }
};

// 昇順にソートされた数列 v において、v[i] >= x となる最小の i を返す.
int LBP(vector<int>& v, int x) {
    return lower_bound(v.begin(), v.end(), x) - v.begin();
}

void merge_sort(vector<int>& a, vector<int>& b, int& num) {
    const int n = a.size();
    if (n == 1) return;
    vector<int> a1, a2, b1, b2;
    for (int i = 0; i < n / 2; ++i) {
        a1.push_back(a[i]);
        b1.push_back(b[i]);
    }
    for (int i = n / 2; i < n; ++i) {
        a2.push_back(a[i]);
        b2.push_back(b[i]);
    }

    vector<int> b1_sorted = b1;
    sort(b1_sorted.begin(), b1_sorted.end());

    merge_sort(a1, b1, num);
    merge_sort(a2, b2, num);

    BIT<int> bit(b1.size());
    for (int bb : b1) {
        int pos = LBP(b1_sorted, bb) + 1;
        bit.add(pos, 1);
    }

    int i = 0, j = 0;
    for (int k = 0; k < n; ++k) {
        if (i != a1.size() && (j == a2.size() || a1[i] < a2[j])) {
            a[k] = a1[i];
            b[k] = b1[i];
            int pos = LBP(b1_sorted, b1[i]) + 1;
            bit.add(pos, -1);
            i++;
        } else {
            int pos = LBP(b1_sorted, b2[j]);
            num += bit.sum(pos);
            a[k] = a2[j];
            b[k] = b2[j];
            j++;
        }
    }
}

int main() {
    int N;
    cin >> N;
    vector<int> a(N), b(N);
    for (int i = 0; i < N; ++i) cin >> a[i];
    for (int i = 0; i < N; ++i) cin >> b[i];
    int ans = 0;
    merge_sort(a, b, ans);
    cout << ans << endl;
}