banner
kanes

kanes

米哈游筆記試験 - Javaバックエンド

私たちは、前iii 個の数から構成されるすべての区間に対して、その「数値凸包区間」の和がちょうど 1 つの連続区間[Pmin,Pmax][P_{\min},P_{\max}][Pmin​,Pmax​](ここで

Pmin=min{a1,,ai},Pmax=max{a1,,ai}P_{\min}=\min\{a_1,\ldots,a_i\},\quad P_{\max}=\max\{a_1,\ldots,a_i\}Pmin​=min{a1​,…,ai​},Pmax​=max{a1​,…,ai​}

)。これは、全体の区間[1,i][1,i][1,i] が 1 つのサブ区間として、その数値凸包が[Pmin,Pmax][P_{\min},P_{\max}][Pmin​,Pmax​] であるためです;他のサブ区間が与える区間はこの区間のサブ区間であり、全体の連続区間を超える数値を拡張することはできません。

したがって、各iii に対して、前の最小値と最大値を維持するだけで済みます。したがって:

  • もしPmin>0P_{\min}>0Pmin​>0(つまり前の接頭辞に 0 が含まれない場合)、全体の集合は000 からPmin1P_{\min}-1Pmin​−1 までの数を含まないため、この場合最小の非負整数は000 です。
  • そうでなければ(つまりPmin=0P_{\min}=0Pmin​=0)、全体の区間[0,Pmax][0,P_{\max}][0,Pmax​] から、すべての000 からPmaxP_{\max}Pmax​までがカバーされていることがわかり、答えはPmax+1P_{\max}+1Pmax​+1 です。

以下に Java の実装を示します。時間計算量はO(n)O(n)O (n) です:

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.StringTokenizer;

public class Main {
    public static void main(String[] args) throws IOException {
        // BufferedReaderを使用して入力を読み取る
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        String line = br.readLine();
        // 最初の行には整数nが含まれる
        int n = Integer.parseInt(line.trim());
        
        // 配列を読み取る
        int[] a = new int[n];
        StringTokenizer st = new StringTokenizer(br.readLine());
        for (int i = 0; i < n; i++) {
            a[i] = Integer.parseInt(st.nextToken());
        }
        
        // 接頭辞の最小値と最大値
        int prefixMin = Integer.MAX_VALUE;
        int prefixMax = Integer.MIN_VALUE;
        
        // 答えを構築する
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < n; i++) {
            prefixMin = Math.min(prefixMin, a[i]);
            prefixMax = Math.max(prefixMax, a[i]);
            
            // 接頭辞に0がない場合、[1,i]のすべての区間には0が含まれないため、答えは0;
            // そうでなければ、答えは接頭辞の最大値に1を加えたもの
            if (prefixMin > 0) {
                sb.append("0");
            } else {
                sb.append(prefixMax + 1);
            }
            if (i < n - 1) {
                sb.append(" ");
            }
        }
        
        System.out.println(sb.toString());
    }
}

コードの説明#

  1. 入力処理
    BufferedReaderStringTokenizerを使用して、大量のデータを効率的に読み取ります。

  2. 接頭辞の最小値と最大値の維持
    ループ内で前iii 個の数の最小値 (prefixMin) と最大値 (prefixMax) を持続的に維持します。

  3. 判断と出力

    • prefixMin > 0の場合、接頭辞に 0 が含まれていないことを示し(つまり 0 より小さい非負数が存在しない)、答えは 0 です。
    • prefixMin == 0の場合、区間のカバー状況に基づいて(全体のサブ区間[0,prefixMax][0, prefixMax][0,prefixMax] からわかるように)、答えはprefixMax + 1です。

この方法は各iii に対して定数時間の操作を行うだけで、全体の時間計算量はO(n)O(n)O (n) であり、n2×105n \le 2 \times 10^5n≤2×105 の要件を満たすことができます。


問題の説明#

長さnnn の 2 進数文字列sss が与えられます。0 と 1 から構成されています。行数がnnn、列数がnnn の正方形の表を構築する必要があります。最初の行は元の文字列sss で、2 行目は文字列sss を右に 1 つ循環移動させたもの、3 行目は文字列sss を右に 2 つ循環移動させたもの、以下同様です。

表中のすべての 0 から構成される三角形または矩形の最大面積を求めてください。

入力の説明#

長さnnn の 2 進数文字列sss が入力されます。0 と 1 の文字のみを含み、1n50001 \leq n \leq 50001≤n≤5000。

出力の説明#

表中のすべての 0 から構成される三角形または矩形の最大面積を出力します。

例 1#

入力#

00110

出力#

6

私たちは、表の構築方法が非常に特殊であり、各行が元の 2 進数文字列の右循環移動であるため、表中の任意の形状(厳密にはその「数値凸包」、つまりその形状に対応するすべてのセルがjij-ij−i(モジュロnnn)から構成される区間)の状態は、元の文字列の環上の連続するゼロのセグメントに依存することを証明できます。簡単な分析を経て、次のことが証明できます:

  • 「矩形」(つまり連続hhh 行、連続www 列)のサブ領域を選択した場合、そのすべてのセルに対応するjij-ij−i 値の集合は

{D(h1),Dh+2,,D+w1}\{\, D - (h-1),\, D-h+2,\dots,\,D+w-1\,\}{D−(h−1),D−h+2,…,D+w−1}

(ここでDDD はあるオフセット)であり、これは実際には長さw+h1w+h-1w+h−1 の整数区間です(注意:区間内の隣接する 2 行の間には重複があります)。明らかに、領域内がすべて 0 であるためには、元の文字列に長さが少なくともw+h1w+h-1w+h−1 の(環上の連続する)ゼロのセグメントが存在する必要があります;全ゼロでない場合(すなわちw+h1<nw+h-1<nw+h−1<n)の場合、最適な選択を行うことで、最大の矩形面積は

R(L)=L+12L+12,R(L)=\lfloor\frac{L+1}{2}\rfloor\cdot\lceil\frac{L+1}{2}\rceil,R(L)=⌊2L+1​⌋⋅⌈2L+1​⌉、

ここでLLL は元の文字列(環状と見なす)におけるゼロの最長連続セグメントの長さを示します。

  • 同様に、「直角三角形」形状(例えば左上隅を直角とし、下に行くごとに前の行より 1 つ多くのセルを使用し、面積は1+2++h=h(h+1)21+2+\cdots+h=\frac{h(h+1)}21+2+⋯+h=2h (h+1)​)を許可する場合、高さをhhh とすると、それが「使用する」jij-ij−i 値はちょうど長さhhh の区間を構成するため、hLh\le Lh≤L である必要があります;このように、最大の三角形面積は

T(L)=L(L+1)2.T(L)=\frac{L(L+1)}2.T (L)=2L (L+1)​です。

簡単に検証できるのは、L2L\ge2L≥2 の場合、

T(L)=L(L+1)2>L+12L+12=R(L),T(L)=\frac{L(L+1)}2> \lfloor\frac{L+1}{2}\rfloor\cdot\lceil\frac{L+1}{2}\rceil=R(L),T(L)=2L(L+1)​>⌊2L+1​⌋⋅⌈2L+1​⌉=R(L)、

すなわち、全ゼロでない場合の最適な面積は「ゼロ三角形」の形状に依存します。注意:元の文字列がすべて 0 の場合、表中のすべてのセルは 0 であり、この場合最大面積は全体の表面積n×nn\times nn×n(n2>n(n+1)2n^2>\frac{n(n+1)}2n2>2n (n+1)​)です。

以上から、まず元の文字列(環状と見なす)をスキャンして、最長連続 0 の長さLLL を求めます(0 が存在しない場合、答えは 0);次に判断します:

  • もしL=nL=nL=n(全ゼロ文字列)、答えはn2n^2n2;
  • そうでなければ、答えはL(L+1)2\dfrac{L(L+1)}{2}2L (L+1)​(つまり、ある「ゼロ三角形」を構成する際の最大面積)です。

以下に Java コードの実装を示します。時間計算量はO(n)O(n)O (n) です:


java

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;

public class Main {
    public static void main(String[] args) throws IOException {
        // 入力の2進数文字列(元の文字列s)を読み取る
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        String s = br.readLine().trim();
        int n = s.length();
        
        // 元の文字列中の(環状に)最長連続0の長さLを計算する
        int L = 0;
        int curr = 0;
        // 一度スキャンする(環の先頭と末尾が接続される場合を考慮しない)
        for (int i = 0; i < n; i++) {
            if (s.charAt(i) == '0') {
                curr++;
                if (curr > L) {
                    L = curr;
                }
            } else {
                curr = 0;
            }
        }
        // 先頭と末尾が両方'0'の場合、環状接続を用いてLを更新する
        if (n > 0 && s.charAt(0) == '0' && s.charAt(n - 1) == '0') {
            int prefix = 0, suffix = 0;
            for (int i = 0; i < n && s.charAt(i) == '0'; i++) {
                prefix++;
            }
            for (int i = n - 1; i >= 0 && s.charAt(i) == '0'; i--) {
                suffix++;
            }
            if (prefix + suffix > L) {
                L = prefix + suffix;
            }
            // 注意:Lは最大でn(元の文字列の長さ)
            if (L > n) {
                L = n;
            }
        }
        
        long ans = 0;
        // 元の文字列に0がない場合、答えは0
        if (L == 0) {
            ans = 0;
        }
        // 元の文字列が全て0の場合、表の各要素は全て0で、答えはn*n
        else if (L == n) {
            ans = (long) n * n;
        }
        // そうでなければ、最適な面積は「ゼロ三角形」に由来し、面積はL*(L+1)/2
        else {
            ans = (long) L * (L + 1) / 2;
        }
        
        System.out.println(ans);
    }
}

コードの説明#

  1. 最長連続 0 のセグメント(環状)を求める
    正方向にスキャンして連続 0 の数を統計します。また、文字列の先頭と末尾が両方 0 である場合、環状に接続できるため、先頭と末尾の 0 の個数を追加で計算し、最長連続数LLL を更新します(ただしLLL は最大でnnn です)。

  2. 答えの判断

    • もしL=0L=0L=0(0 がない)、答えは 0 です。
    • もしL=nL=nL=n(全 0 文字列)、全表は 0 で、答えはn×nn \times nn×n です;
    • そうでなければ、答えはL(L+1)2\frac{L(L+1)}{2}2L (L+1)​(これはある「ゼロ三角形」を構成する際の最大面積)です。

このようにして、私たちはO(n)O(n)O (n) 内で表中のすべての 0 から構成される(三角形または矩形)領域の最大面積を求めることができました。


小結#

表中の各セルの値は、s[(ji)modn]s[(j-i) \bmod n]s [(j−i) modn] に依存するため、全問題は元の文字列(環状)中の最長のゼロセグメントの長さLLL を求めることに変換されます;そして、全ゼロでない場合、「ゼロ直角三角形」の面積は

L(L+1)2,\frac{L(L+1)}2,2L(L+1)​、

常に同様に制限された矩形の面積よりも大きいことが証明されます。したがって、最終的な答えは

  • もしsss が全て 0 であれば:答えはn2n^2n2;
  • そうでなければ:答えはL(L+1)2\frac{L(L+1)}{2}2L (L+1)​です。

この考え方は、時間計算量を低下させるだけでなく(O(n)O(n)O (n))、また、行列を構築する際の「循環移位」という特殊な性質を利用しています。


問題の説明#

ミ小遊は配列を手に入れました。彼は何度かの問い合わせを持っており、各問い合わせで整数xxx を入力します。彼は、xxx が配列の 2 つの要素の積から得られるかどうかを判断したいと考えています。数学的に表現すると、2 つのインデックスiii とjjj(i<ji < ji<j)が存在し、ai×aj=xa_i \times a_j = xai​×aj​=x であることを満たす必要があります。

入力の説明#

最初の行には正整数nnn が入力され、配列のサイズを示します。

次の行にはnnn 個の正整数aia_iai​が入力され、配列の要素を示します。

3 行目には正整数qqq が入力され、問い合わせの回数を示します。

次のqqq 行には、各行に正整数xxx が入力され、1 回の問い合わせを示します。

  • 1n,q1051 \leq n, q \leq 10^51≤n,q≤105
  • 1ai,x1061 \leq a_i, x \leq 10^61≤ai​,x≤106

出力の説明#

各問い合わせに対して、2 つの数の積がxxx に等しい場合、出力は-1 -1です。

そうでなければ、iii とjjj を出力します(i<ji<ji<j)、空白で区切って、ai×aj=xa_i \times a_j = xai​×aj​=x を満たします。解が複数ある場合は、任意のものを出力してください。

例 1#

入力#

5
1 2 3 2 4
2
4
5

出力#

2 4
-1 -1

説明#

  • 最初の問い合わせでは、出力1 5も可能です。
  • 2 番目の問い合わせでは、明らかに 2 つの要素の積が 5 になることはできません。

以下に Java の実装を示します。考え方は、配列内の各数字が配列内で最初に出現した位置と 2 回目に出現した位置を記録することです。インデックスは 1-indexed です。したがって、2 つの数字aaa とbbb に関して:

  • もしaba \neq ba\=b であれば、配列内に両方が出現したかどうかを確認するだけです;
  • もしa=ba=ba=b であれば、2 回以上出現する必要があります。

各問い合わせxxx に対して、ddd を 1 からx\sqrt{x}x​まで列挙し、ddd がxxx を割り切る場合、候補の別の因子はd2=x/dd_2=x/dd2​=x/d です。候補の対について:

  • もしd==d2d==d_2d==d2​であれば、配列内でその数字が少なくとも 2 回出現する必要があります。
  • そうでなければ、配列内にddd とx/dx/dx/d が同時に出現しているかどうかを確認します。

出力時にはi<ji<ji<j の順序を満たすことに注意し、2 つのインデックスをソートして出力します。

時間計算量は、各問い合わせで最大x\sqrt{x}x​個の因子を列挙するため、x1×106x\le1\times10^6x≤1×106 であるため、各問い合わせは最大約 1000 回のループを持ち、全体の時間計算量はO(qxO(q\sqrt{x})O (qx​) です。

以下に完全なコードを示します:


java

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.StringTokenizer;

public class Main {
    public static void main(String[] args) throws IOException {
        // BufferedReaderを使用して入力効率を向上させる
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        StringTokenizer st;

        // 配列のサイズnを読み取る
        st = new StringTokenizer(br.readLine());
        int n = Integer.parseInt(st.nextToken());

        // 数字の範囲(1 <= a_i <= 1e6)に基づいて、サイズ1e6+1の配列を事前に割り当てる
        int MAX = 1000000;
        // firstIndex[v]は数字vが配列内で最初に出現した位置(1-indexed)、未出現の場合は0
        int[] firstIndex = new int[MAX + 1];
        // secondIndex[v]は数字vが配列内で2回目に出現した位置(1-indexed)、2回未満出現の場合は0
        int[] secondIndex = new int[MAX + 1];

        // 配列要素を読み取る
        st = new StringTokenizer(br.readLine());
        for (int i = 1; i <= n; i++) {
            int num = Integer.parseInt(st.nextToken());
            if (firstIndex[num] == 0) {
                firstIndex[num] = i;
            } else if (secondIndex[num] == 0) {
                secondIndex[num] = i;
            }
        }
        
        // 問い合わせの回数qを読み取る
        int q = Integer.parseInt(br.readLine().trim());
        StringBuilder sb = new StringBuilder();
        
        // 各問い合わせを処理する
        for (int qi = 0; qi < q; qi++) {
            // 各問い合わせでxを指定
            int x = Integer.parseInt(br.readLine().trim());
            boolean found = false;
            
            // 可能な因子dを1からsqrt(x)まで列挙
            int sqrtX = (int) Math.sqrt(x);
            for (int d = 1; d <= sqrtX; d++) {
                if (x % d != 0) {
                    continue;  // dはxの因子ではない
                }
                
                int d2 = x / d;
                // dまたはd2が配列内の可能な数字の範囲を超えている場合、直接スキップ
                if (d > MAX || d2 > MAX) {
                    continue;
                }
                
                if (d == d2) {
                    // 2つの因子が同じ場合、2回以上出現する必要がある
                    if (firstIndex[d] != 0 && secondIndex[d] != 0) {
                        sb.append(firstIndex[d]).append(" ").append(secondIndex[d]).append("\n");
                        found = true;
                        break;
                    }
                } else {
                    // 2つの因子が異なる場合、両方が出現しているだけでよい
                    if (firstIndex[d] != 0 && firstIndex[d2] != 0) {
                        int i1 = firstIndex[d];
                        int i2 = firstIndex[d2];
                        // 2つのインデックスを出力し、i1 < i2を保証する
                        if (i1 > i2) {
                            int temp = i1;
                            i1 = i2;
                            i2 = temp;
                        }
                        sb.append(i1).append(" ").append(i2).append("\n");
                        found = true;
                        break;
                    }
                }
            }
            
            if (!found) {
                sb.append("-1 -1\n");
            }
        }
        
        System.out.print(sb.toString());
    }
}

コードの説明#

  1. 事前処理
    与えられた配列に対して、各数字の最初と 2 回目の出現位置を記録するために、2 つの配列firstIndexsecondIndexを利用します。これにより、後で同じ数字が 2 つの要素の積としてxxx を満たす候補となるかどうかを判断できます。

  2. 問い合わせ処理
    各問い合わせxxx に対して、1dx1 \leq d \leq \sqrt{x}1≤d≤x​を列挙します:

    • もしddd がxxx の因数でない場合、スキップします;
    • ddd が平方根(すなわちd=x/dd = x / dd=x/d)である場合、少なくとも 2 回出現するかどうかを確認します;
    • そうでなければ、数字ddd とx/dx/dx/d が配列内に同時に出現しているかどうかを確認します。
  3. 出力
    条件を満たす因子対が見つかった場合、配列内のインデックスを出力します(i<ji<ji<j を満たすように);すべての候補が条件を満たさない場合は-1 -1を出力します。

このアルゴリズムの全体の時間計算量はO(qxO(q\sqrt{x})O (qx​) であり、q105q\le 10^5q≤105 の状況に対応できます。

この文はMix Spaceによって xLog に同期更新されました。元のリンクはhttps://blog.kanes.top/posts/WrittenExamination/547345546732です。

読み込み中...
文章は、創作者によって署名され、ブロックチェーンに安全に保存されています。