web-dev-qa-db-ja.com

文字列の異なるサブシーケンスの数を見つける方法は?

これは別の spoj problem で、文字列の異なるサブシーケンスの数を見つける方法を尋ねます?

例えば、

入力
AAA
ABCDEFG
コーデック

出力
4
128
496

この問題を解決するにはどうすればよいですか?

40
user467871

これは古典的な動的プログラミングの問題です。

みましょう:

dp[i] = number of distinct subsequences ending with a[i]
sum[i] = dp[1] + dp[2] + ... + dp[i]. So sum[n] will be your answer.
last[i] = last position of character i in the given string.

Null文字列には1つのサブシーケンスがあるため、dp[0] = 1です。

read a
n = strlen(a)
for i = 1 to n
  dp[i] = sum[i - 1] - sum[last[a[i]] - 1]
  sum[i] = sum[i - 1] + dp[i]
  last[a[i]] = i

return sum[n]

説明

dp[i] = sum[i - 1] - sum[last[a[i]] - 1]

最初は、前の文字で終わるすべてのサブシーケンスにa[i]を追加できると想定していますが、これは、カウントされたサブシーケンスを区別する必要があるという条件に違反する可能性があります。 last[a[i]]は、a[i]がこれまでに表示された最後の位置を示していることに注意してください。私たちが過大評価する唯一のサブシーケンスは、前のa[i]が追加されたものなので、それらを差し引きます。

sum[i] = sum[i - 1] + dp[i]
last[a[i]] = i

定義に従ってこれらの値を更新します。

インデックス作成が0から始まる場合は、a[i - 1]をどこで使用しても、a[i]を使用します。コードを送信する場合は、計算をmod関数でラップすることも忘れないでください。これは次のように実装する必要があります:

mod(x) = (x % m + m) % m

一部の言語(C/C++など)で負の値を正しく処理するため。

61
IVlad

この問題を解決する簡単な方法があります。

アイデアは:文字列のすべての文字が異なる場合、サブシーケンスの総数は2^n.これで、以前にすでに発生した文字を見つけた場合は、その最後の発生のみを考慮する必要があります(それ以外の場合、シーケンスは区別されません)。そのため、前の発生によるサブシーケンスの数を差し引く必要があります。

私の実装は次のとおりです:

read s
dp[0] = 1
len = strlen(s)
last[s.length()] = {-1} //declaring `last` array with same as length of string `s` and all elements initialized with -1.

for (i = 1; i <= len; i++) 
{
    dp[i] = (dp[i - 1] * 2)
    if (last[s[i]] > 0) dp[i] = (dp[i] - dp[last[s[i]] - 1])
    last[s[i]] = i
}
33
Mostafiz Rahman

これが私の[〜#〜] code [〜#〜]です:

_#include<iostream>
typedef long long ll;

ll fun(std::string s,ll visited[256],ll n,ll L[]){  

    ll ans=0;
    if(n<0){
        return 1;
    }
    //std::cout<<s.substr(0,n+1)<<" "<<n<<endl;

    ans=fun(s,visited,n-1,L);
    L[n]=ans;
    ans=ans*2;
    if(visited[int(s[n])]>=0){
        ans -= L[visited[int(s[n])]];
    }
    visited[int(s[n])]=n;

    return ans;

}
int main(){

    std::string s;
    std::cin>>s;
    ll n=s.length();

    ll visited[256];
    ll L[n];
    memset(visited,-1,sizeof(visited));
    memset(L,-1,sizeof(L));

    std::cout<<fun(s,visited,n-1,L);

    return 0;
}
_

説明

文字列の後ろから、つまり最後の要素から最初の要素までスキャンするので、再帰でさらにスキャンするために最初の_n-1_文字を送信します。

n==-1 or n<0(both are same)になると、空の文字列に到達し、1を返します。空の文字列のサブシーケンスの数は1です。

したがって、再帰から戻るときに、現在の重複しない文字を前の文字列に追加すると、noが2倍になることがわかります。サブシーケンスの。これで、以前のすべてのサブシーケンスの最後にこの文字を追加できるため、重複が発生します。したがって、withおよびwithoutこの文字は、以前のすべてのサブシーケンスの2倍を意味します。

現在の文字が重複ではないと仮定して、私は前の文字を乗算します。サブシーケンスの2。

合計後。最初の_n-1_文字のサブシーケンスの計算が完了したら、最初のn文字に対してそれらを2倍にします。

ただし、現在遭遇している文字(n番目の文字)が最初の_n-1_文字の前にすでに存在していると仮定します(つまり、文字列s [0 .... n-1]内で見つかります(注:s [n]は現在の文字です))、それからそれらを差し引く必要があります。この現在の文字に最後に遭遇し、L ['この特定の文字']にすでに計算および格納されているsのその部分(を除く)までの可能なサブシーケンスの数。

ie-BACA-与えられた文字列について、4番目のAは以前にすでに遭遇しています(再帰から戻るときに、最初にBに遭遇し、次にA、次にC、最後にA)なので、noを差し引きます。 2番目のA(これは2です(Aの前のサブシーケンスの数は2であるため))までに計算されたサブシーケンスの数)。

したがって、毎回noを計算しました。最初の_n-1_文字のサブシーケンスの場合、それらを配列Lに格納します。

通知:L [k] noを保存します。 k番目のインデックスの前のサブシーケンスの。

Visited配列を使用して、現在存在している特定の文字が既にスキャンされているかどうかを確認しました。

現在の文字に遭遇したら、訪問した配列を現在の位置をnに更新します。重複するシーケンスを除外する必要があるため、これを行う必要があります。

:_visited[]_は、文字列s内の任意の文字の位置が負ではないため(0ベースのインデックス)、すべて-1で初期化されます。

概要

How do you arrive at the number of duplicates? Let's say the last occurrence of current character at i, was at j'th position. Then, we will have duplicate subsequences: consider starting with i'th character and then all subsequences possible from [0,j-1] vs. starting at j'th character and then all subsequences possible from [0,j-1]. So, to eliminate this, you subtract the number of subsequences possible from upto (excluding) j with L[0]=1 mean that upto(excluding 0), no. of subseq are 1(empty string has 1 subsequence).

0
AJ.S