web-dev-qa-db-ja.com

3で割り切れる部分文字列の数を数える効率的なアルゴリズム

10進数の文字列が与えられた場合、LからR [両方を含む]の範囲で3で割り切れるすべての部分文字列の数を見つける必要があります。ここで、LとRは指定された文字列のインデックス[1ベース]です。
string length <= 100000

私はすべての可能な部分文字列を反復して答えを取得する単純なアプローチを試しましたが、特に複数のペアのLとRの場合、それは十分に速くありません。

次に、DPアプローチを試しました。文字列全体で3で割り切れるすべての可能な部分文字列を取得できます。つまり、指定された範囲の結果を与えるDPソリューションをコーディングできません。
私が試したDPソリューション(完全にわかりません):

for(i=0 ; i<n ; i++) {
    for(j=0 ; j<3 ; j++) {
        dp[i][j]=0 ;
    }
    int curr = (input[i]-'0')%3 ;
    dp[i][curr]++ ;
    if(i) {
        for(j=0 ; j<3 ; j++) {
            if(curr % 3 == 0) { dp[i][j] += dp[i-1][j]; }
            if(curr % 3 == 1) { dp[i][j] += dp[i-1][(j+2)%3]; }
            if(curr % 3 == 2) { dp[i][j] += dp[i-1][(j+1)%3]; }
        }
    }
}
long long sum = 0;
for(i=0 ; i<n ; i++) { sum += dp[i][0] ; }

このソリューションを変更して、特定の範囲[L、R]の結果を得ることができますか?

たくさん検索した後、範囲クエリの問題はセグメントツリーによって解決されることを学びましたが、この質問でセグメントツリー+遅延伝播がどのように役立つかわかりません。

それでは、LからRの範囲で[3で割り切れるすべての部分文字列をカウントするための高性能な方法は何ですか?

編集:
入力:最初の行には指定された文字列が含まれています。その後の行には、それぞれLとRを表す2つの整数が含まれています。ここで、LとRは両方とも文字列のインデックス(1ベース)です。

入力:
301524 1 2 4 6 3 5

出力:
3 1 1

説明:
L = 1&R = 2の場合、3つの可能な部分文字列、{3}, {0}, {30}&これらすべてを10進数と見なした場合、3で割り切れます。したがって、出力です。
L = 4&R = 6の場合、6つの可能なサブストリング、{5} , {52}, {524}, {2}, {24}, {4}&これらのうち{24}は3で割り切れます。

3333のような部分文字列の繰り返しは複数回カウントされるため、L = 1からR = 4の場合、答えは10になります。

4
Aalok

かわいい動的プログラミング問題。ここにJavaソリューションと簡単な説明があります。

自問する基本的な質問は、副問題Xを知っていれば、問題Yを簡単に解決できるでしょう。

この場合、問題Yは3で割り切れる部分文字列の数であり、サブ問題Xは可能な各modの前の文字で終了する3を法とする部分文字列の数です(つまり、0、1、および2のままです)。

以前の位置で、0の残基、3の残基で1、1の残基で2の2つのサブストリングが終了していて、現在の数とその残基が与えられている場合、それは自明です。現在の文字で終了するすべての文字列の剰余を決定します。

現在の数の剰余が1(たとえば、数が1、4、または7)である場合、1の剰余で前の数で終了する部分文字列は、2の剰余を持ち、2の剰余である部分文字列は、ゼロの剰余、およびゼロの剰余を持つものは、長さ1の新しい可能な部分文字列を追加したため、現在の桁に1プラス1の剰余があります。

たとえば、文字列521438があり、各残基の3で終了する文字列の数がわかっている場合(残基0、1、2はそれぞれ2、2、1)、8 mod 3は2なので、残基0を持つものすべてに残基2があり、残基2を持つものすべてに残基1があり、残基1を持つものすべてに残基0があることを知っているため、(2、1、および2をそれぞれ)さらに、残基の新しい文字列ができます。 2なので、現在の数を含む2、1、3になります。

これを線形時間で処理した後、すべての部分文字列について、すべての場所で終了する残余ゼロを持つすべての部分文字列を合計します。

これがコードです:

// Takes constant space and linear time.
public static void main(String[] args) {

    // You really only need these numbers mod 3.
    int[] s = new int [] { 5,8,1,4,6,2 };
    int left = 3;
    int right = 4;

    int[] found = new int[3];
    int[] last_found = new int[3];
    int sum_found = 0;

    for(int i = left; i <= right; ++i) {

        int res = s[i-1] % 3;

        // leaving the +0 just to show the symmetry.
        // Basically, rotate by the residue and +1 for the new substring.
        // This can be done as a single array, but this is clearer I think.
        // (Also, a proper mathematical modulus would be easier too.)
        found[(res+0) % 3] = last_found[0] + 1;
        found[(res+1) % 3] = last_found[1];
        found[(res+2) % 3] = last_found[2];

        sum_found += found[0];

        // Swap the current and last arrays to make top of the loop simpler.
        int[] swap = last_found;
        last_found = found; 
        found = swap;
    }

    System.out.println( sum_found );
}

コード編集

上記のコードはテーブルを削除し、最後の位置を追跡するだけです。これは、長さが3の2つの配列と、それらの間のスワッピングで行われます。単一の配列で実行することもできますが、コードが複雑になります(おそらく、マイクロ最適化の意味でもパフォーマンスがよくありません)。

これで、LeftおよびRightの要求に従いながら、線形の時間と一定の空間になります。これは、他の多くのDPアルゴリズムと同様です。各反復がIth-1反復のみを振り返っていることに気付いた場合、通常はテーブル全体を省略できます。

また、合計とウェイを追跡します(最終的な配列も存在しないため、現在は必須です)。最初は問題を完全には理解していませんでしたが、途中でいくつかの編集を行ったようです。

6
JasonN

これがPython問題の解決策です。基本的には@JasonNの解決策と同じです。また、LとRの異なるペアの値を返す関数を実装しています。事前計算を行うときのメモリ。実行できます ここ

#http://programmers.stackexchange.com/q/268022/51441


# s:
#   input digit string that should be checked
# L:
#   the start of the substring that should be investigated
# L:
#   the end of the substring that should be investigated
# cnt:
#   number of 3divisble substrings found so far
# ss0:
#   number of substrings that end at the current position
#   and are divisible by 3
# ss1:
#   number of substrings that end at the current position
#   and have the remainder 1 when divided by 3
# ss2:
#   number of substrings that end at the current position
#   and have the remainder 2 when divided by 3


def ss(s,L,R):
    cnt=0
    (ss0,ss1,ss2)=(0,0,0)
    for i in range(L,R+1):
        r=int(s[i])%3
        if r==0:
            ss0+=1
        Elif r==1:
            (ss0,ss1,ss2)=(ss2,ss0,ss1)
            ss1+=1
        Elif r==2:
            (ss0,ss1,ss2)=(ss1,ss2,ss0)
            ss2+=1
        cnt+=ss0
    return(cnt)

print(ss('392301',0,len('392301')-1))
print(ss('2035498',2,5))
1
miracle173

数字の合計が3で割り切れる場合、数値は3で割り切れる(3の割り算規則に従って)。クエリごとにO(n^2)動的プログラミングソリューション(前処理)とO(1)定数時間が存在します。

arr[i]が指定された要素(1....N)を含む配列であり、query[i][j][i, j]から3で割り切れる部分文字列の数を示すとします。

for(int i = 1; i <= N; i++) {
    query[i][i] = (arr[i] % 3 == 0); // i.e. # of substrings divisible by 3 in [4, 4] is 1 if arr[4] is divisble by 3 otherwise 0
    arr[i] += arr[i - 1]; // arr[i] will contain cumulative sum of 1...i
}

// 
for(int i = N - 1; i > 0; i--) {
    for(int j = i + 1; j <= N; j++) {
        // exclusion-inclusion principle. # of substrings in [1, 7] will be - 
        // #1 sum of # of substrings in [1, 6] and [2, 7]. 
        //    As [2, 6] is included two times, so we need to subtract it one time
        // #2 plus 1 if substring [1, 7] is divisble by 3, 0 otherwise

        // #1
        query[i][j] = query[i][j - 1] + query[i + 1][j] - query[i + 1][j - 1];
        // #2
        // arr[j] contains sum of 1...j. 
        // so arr[j] - arr[i - 1] contains sum of [i...j]
        // number constructed from substring [i...j] is divisble by 3 
        // iff summation of its digits is divible by 3
        query[i][j] += ((arr[j] - arr[i - 1]) % 3 == 0);
    }
}

注:これは、要素の数(Range)が3 * 10^3内にある場合に機能します。それ以外の場合、query[Range][Range]の縮退はコンパイルエラーを示します。

それでもこれらのループで何が起こっているのかわからない場合は、ペンと紙を使用して2Dテーブルを描画し、上記の計算に従ってそれを埋めてください。その後、あなたは確かに明確な直感を得るでしょう:)

0
Kaidul Islam