接頭辞の合計と差 --- 概念 + 例

目次

プレフィックスと

コンセプト

違い

コンセプト


プレフィックスと


コンセプト

高校でシリーズの概念を学びましたが、これは現在の配列に非常に似ています。同様に、配列の前置合計と配列の最初の n 個の項目の合計は、実際には概念と見なすことができます。多くのアルゴリズムの問​​題は、プレフィックスサムのアイデアを使用しています。つまり、高校の最初の n 個のアイテムの合計を使用して、配列内の任意の間隔の要素の合計を解くことができます。その式は次のとおりです。

S{_n} = S{_{n - 1}} + a{_n}

実用的な例と組み合わせる: 配列 arr があるとします。

arr = {1,2,3,4,5};

ここで、添字の間隔が [i, j] である配列内の要素のサイズ Sum を見つける必要があります。計算式は次のとおりです。

合計 = S{_j} - S{_{j - 1}}

注: 配列の添え字は 0 からカウントされます。

具体的な手順に関しては、プレフィックスサムの操作方法は主に次の手順に分けられます。

  • 前処理: 最初に配列要素の合計を見つけますS{_n} (n は配列の長さです)
  • a[i] は、0 から i までの添え字を持つ要素の合計を表します。
  • 特定の間隔の合計を見つける場合、上記の結論を使用できます。

アルゴリズムの時間計算量分析:        

プレフィックス サム アルゴリズムの時間計算量は、O(n) に達する可能性があります。

1. リンク: 3956. 切り詰められた配列 - AcWing 問題バンク

 

アイデアは右の図に示されており、問題を解決するための手順を取得することは難しくありません。

  1. 最初に配列をトラバースし、前処理して配列の合計 s を見つけます。s が 3 の倍数でない場合は、解がないことを意味します。
  2. 2点目を前から後ろに列挙し、このときの配列の前置和が (3 / s) * 2 であると判断します。
    1. はい: 次に、最初のポイントを列挙し、前の配列のプレフィックスの合計が 3/s であるポイントの数を見つけます。これは cnt です。
    2. いいえ: 次のサイクルに進みます
  3. 毎回取得した cnt を合計して、すべてのスキーム ans の出力を取得します。終了。

この質問の Java コードは次のとおりです。

import java.util.Scanner;

import static java.lang.System.exit;

public class Acwing_3959 {
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        int n = scanner.nextInt();
        int[] nums = new int[n + 5];
        int[] s = new int[n + 5];  // 表示数组的前n项和
        long ans = 0, sum = 0, cnt = 0;
   // ans表示总的方案数,sum表示数组的综合,cnt表示在当前元素之前有多少个第一个元素符合要求
        for (int i = 1; i <= n; i++) {
            nums[i] = scanner.nextInt();
            sum += nums[i];  // 计算数组的总和
            if(i == 1) s[i] = nums[i];
            else s[i] = nums[i] + s[i - 1];   // 计算数组的前n项和
        }
        if(sum % 3 != 0 || n < 3) {
            System.out.println(0);
            exit(0);
        }
        long average = sum / 3;
        for (int i = 1; i < n; i++) {
            if(s[i] == average * 2) ans += cnt; 
            if(s[i] == average) ++cnt;
        }
        System.out.println(ans);
    }
}

この質問を完了する際には、次の点に注意してください。

  • 添字 1 から接頭辞と配列を開始することをお勧めします。これにより、配列要素がすべて 0 になり、部分配列の数を計算するのが困難になる状況を回避できます。

違い


コンセプト

前にプレフィックスサムについて学びましたが、プレフィックスサムと非常によく似たアルゴリズムのクラスに違いがあります。同じことが文字列に対して行われ、接頭辞の合計は o(1) 時間で計算でき、配列の特定の範囲内の要素の合計を計算でき、差を特定の範囲内の要素に追加できます。平均 o(n) 時間 (または減算) 数の配列の範囲。

  アルゴリズムのアイデアは次のとおりです。

配列内の添字 2 から 3 の要素に数値 c を追加する必要があると仮定すると、微分配列 b を維持できます. 最初は、b のすべての要素が 0 であり、b[2] を c に変更し、同時に b[4] を変更 -c に変更し、差分配列の前置和 s[i] を計算します。s[i] は添え字 i の前置和を意味します。最後に、元の配列の要素を、対応する差分配列の前置合計に追加します。このメソッドの時間計算量が o(n) であることを証明することは難しくなく、操作の数とは関係ありません。つまり、差分アルゴリズムを使用すると、配列の間隔を何度増やしても、アルゴリズムの時間計算量は常に O(n) になります。

差分配列でのプレフィックスの合計は簡単な操作であることに注意してください。配列要素を追加するには、元の配列と差分配列をトラバースする必要があるため、元の配列を追加する前に前置合計を使用するだけで済みます S{_n} = S{_{n - 1}} + a{_n}. 前置合計配列を使用すると、アルゴリズムの全体的な時間の複雑さに影響を与えることなく計算を完了することができます. プレフィックスと .

 

上記のステートメントは、実際の問題解決でよく使用される一種の考え方であり、ここで違いの定義が与えられます。

定義: 微分配列の各要素は、元の配列の現在の要素とその前の要素 (i≠0) の差であり、微分配列の最初の要素は元の配列の最初の要素と同じです。

推論: 差分配列の前置和が元の配列です。

証明: 元の配列が次のとおりであるとします。

                        int arr = {2, 3, 4, 6, 7};

  次のように差分配列を見つけるのは難しくありません。

                        int b = {2, 1, 1, 2, 1}; この差分配列の前置和を計算して、元の配列を取得します。

一般的な場合でも、この推論が成り立つことを証明することは難しくありません。

追記:実際の運用では、差分配列や要素群の添字を1から数える必要があります。作者のレベルの制限により、具体的な原則は今のところは言えませんが、練習を重ねてください。実際、多くの問題は仮想的に回避することができます。

トピック リンク: 3729. 配列要素の変更 - AcWing Question Bank

 

図 1 Acwing_3729 (例 1 のスクリーンショット)

問題解決のアイデア:

差分の考え方により、出力する配列を元の配列とみなすことができ、差分配列をプログラム内に保持します。経験によると、差分配列と元の配列の添字は両方とも 1 から開始する必要があります。「論理加算」の概念は、元の配列への微分配列の前置和の加算、つまり 1 + 1 = 1 を完了するために使用されます。元の配列の最後の 3 つの要素を 1 に変更する要件は、元の配列の長さは変化していますが、毎回論理的に 1 を加算する必要がある要素の前後の添え字を見つけるのは難しくありません. すると、この質問の全体的な要件は元の配列の指定された間隔に論理的に 1 を追加するには、多くの操作があります。一般的な方法を使用すると、制限時間を確実に超えることは難しくありません。そのため、違いの方法は、この質問に対する肯定的な解決策の 1 つです。

問題解決の手順は次のとおりです。

  1. すべて 0 の差分配列を準備します。長さは十分に大きいです。
  2. 元の配列と問題設定の整数配列を同時にトラバースする. 問題設定の整数配列は一度しか使えないので, 配列のダンプをキャンセルし, 配列の入力と利用を組み合わせることができる.また、配列の代わりに変数 ret を使用できます。
  3. 得られた re を微分配列の添字間隔に変換し (添字間隔が [i - ret + 1, i +1] であることを証明することは難しくありません)、対応する微分配列の 2 つの要素 (k [i - ret + 1] および k[i + 1]) プラス 1 および -1。
  4. 差分配列のプレフィックスと配列を維持し、元の配列に追加します。注: 前置和配列は 1 の多重度を表し、1 は 1 が 1 つ追加されることを意味し、2 は 2 つの 2 が追加されることを意味するため、元の配列に微分配列の前置和を追加する場合、「論理和」は使用できません。 . すべてのプレフィックスと配列の処理が完了して初めて、1 より大きい数を 1 に変更し、1 より小さい数を 0 に変更できます。
  5. 論理処理後、元の配列を出力します。終了。

この質問のJavaは次のとおりです。

import java.util.Scanner;

public class Acwing_3729 {
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        int t = scanner.nextInt();
        int[] k = new int[100005];   // 差分数组
        while(t-- > 0){
            int n = scanner.nextInt();
            int[] arr = new int[n + 5];
            for(int i = 1; i <= n; i++){
                int ret = scanner.nextInt();
                if(i - ret + 1 <= 1) k[1] += 1;
                else k[i - ret + 1] += 1;
                k[i + 1] += -1;
            }
            for (int i = 1; i <= n; i++) {
                arr[i] = arr[i - 1] + k[i];
                k[i] = 0;
                System.out.printf("%d ", arr[i] > 0 ? 1 : 0);
            }
            k[n + 1] = 0;
            System.out.println();
        }
    }
}

 PS: ステーション c のリッチ テキスト エディタは非常に使いにくく、以前のブログはワードで書かれており、このブログに切り替えるのに時間がかかりました。特に数式エディタは本当に不快です。

@SoftwareTeacher は以下を最適化できますか~~~~~

おすすめ

転載: blog.csdn.net/qq_61567032/article/details/129152315