1. 程式人生 > >LIS LCS LCIS (主要過一遍,重在做題)

LIS LCS LCIS (主要過一遍,重在做題)

只詳細講解LCS和LCIS,別的不講…做題優先。

先得理解最長上升子序列吧,那個HDOJ攔截導彈系列可以做一下,然後用O(nlog(n))的在做一遍

然後就是真正理解LCS;

真正理解源於做題,做題就像查漏補缺一樣,你總有不會的地方。
這裡寫圖片描述

【完全的求一個最長公共子序列】
(非常徹底地理解路徑或者說是狀態轉移的規律)
先是初始化
付一個0的dp陣列,把dp作為一個介體達到一種最長公共子序列的目的
然後就開始更新dp的值,dp的狀態轉移方程OK,然後根據狀態轉移方程,
可以很清楚地知道,路徑的變化就是狀態轉移變化的方向。【這樣就可以實現路徑的初始模型】

【詳細闡述路徑】
就是標記啊,因為LCS問題上面,狀態轉移只有三種,向下,向右,還有右下,然後就是搞三個標記,在豎直方向上移動的話就是-1,在水平方向上移動的話就是1,然後如果滿足了相等,要斜對角移動的話就是0,然後遞迴進行輸出。
【反著的一個題目(拓展)】

另一個問題就是給你兩個序列,再給你一個序列,然後問你前面兩個序列能否組成被給的第三個序列。

DP求解:定義dp[i][j]表示A中前i個字元與B中前j個字元是否能組成C中的前 (i+j) 個字元,如果能標記true,如果不能標記false;
滿足什麼狀態可以轉變成什麼狀態;

【小優化】
用個滾動陣列;減少記憶體;

【徹底理解的LCS的應用】

Hdoj3779,hdoj1501,poj2192,poj2250,poj1159

【補】

【LIS的nlog(n)】

int a[1010];
int d[1010];

int main()
{
    int n;
    while(~scanf("%d"
,&n)) { for(int i=1;i<=n;i++) scanf("%d",&a[i]); d[1]=a[1]; int len=1; int j; /* 嚴格單調遞增子序列就是lower_bound();非嚴格單調子序列就是upper_bound(); */ for(int i=2;i<=n;i++) { if(a[i]>=d[len]) {len++;d[len
]=a[i];} else{ int pos=upper_bound(d+1,d+len,a[i])-d;//非嚴格單調 d[pos]=a[i]; } } printf("%d\n",len); } return 0; }

【最長公共上升子序列(LICS)】
題分成兩種,一個是在求個LICS個數,還有
一個是要求求LICS的樣子codeforces的10D好像

下面的解釋來自百度/各種部落格…集結【慢慢看】

//某大神的幾句話的事:http://www.cnblogs.com/ka200812/archive/2012/10/15/2723870.html
設題目給出a[],b[]兩個序列。f[j]表示b序列到j的時候,與a[??]序列構成最長公共上升子序列的最優解。其中a[??]序列,從1到n列舉過來。
如果某一個時刻a[i]==b[j],那麼顯然,我們就應該在0到j-1中,找一個f值最大的來更新最優解。這和求上升子序列是思想是一樣的。另外,在列舉b[j]的時候,我們順便儲存一下小於a[i]的f值最大的b[j],這樣在更新的時候,我們就可以做到O(1)的複雜度,從而將整個演算法的複雜度保證在O(nm)
輸入案例
/*
10
7 10 1 2 1 7 1 5 9 9
9
6 2 5 6 7 7 5 5 2
*/

看不懂大神的幾句話,看這個…【大自然的搬運工】…(來自百度)
預備知識:動態規劃的基本思想,LCS,LIS。
問題:字串a,字串b,求a和b的LCIS(最長公共上升子序列)。
首先我們可以看到,這個問題具有相當多的重疊子問題。於是我們想到用DP搞。DP的首要任務是什麼?定義狀態。
1定義狀態F[i][j]表示以a串的前i個字元b串的前j個字元且以b[j]為結尾構成的LCIS的長度。
為什麼是這個而不是其他的狀態定義?最重要的原因是我只會這個,還有一個原因是我知道這個定義能搞到平方的演算法。而我這隻會這個的原因是,這個狀態定義實在是太好用了。這一點我後面再說。
我們來考察一下這個這個狀態。思考這個狀態能轉移到哪些狀態似乎有些棘手,如果把思路逆轉一下,考察這個狀態的最優值依賴於哪些狀態,就容易許多了。這個狀態依賴於哪些狀態呢?
首先,在a[i]!=b[j]的時候有F[i][j]=F[i-1][j]。為什麼呢?因為F[i][j]是以b[j]為結尾的LCIS,如果F[i][j]>0那麼就說明a[1]..a[i]中必然有一個字元a[k]等於b[j](如果F[i][j]等於0呢?那賦值與否都沒有什麼影響了)。因為a[k]!=a[i],那麼a[i]對F[i][j]沒有貢獻,於是我們不考慮它照樣能得出F[i][j]的最優值。所以在a[i]!=b[j]的情況下必然有F[i][j]=F[i-1][j]。這一點參考LCS的處理方法。
那如果a[i]==b[j]呢?首先,這個等於起碼保證了長度為1的LCIS。然後我們還需要去找一個最長的且能讓b[j]接在其末尾的LCIS。之前最長的LCIS在哪呢?首先我們要去找的F陣列的第一維必然是i-1。因為i已經拿去和b[j]配對去了,不能用了。並且也不能是i-2,因為i-1必然比i-2更優。第二維呢?那就需要列舉b[1]..b[j-1]了,因為你不知道這裡面哪個最長且哪個小於b[j]。這裡還有一個問題,可不可能不配對呢?也就是在a[i]==b[j]的情況下,需不需要考慮F[i][j]=F[i-1][j]的決策呢?答案是不需要。因為如果b[j]不和a[i]配對,那就是和之前的a[1]..a[j-1]配對(假設F[i-1][j]>0,等於0不考慮),這樣必然沒有和a[i]配對優越。(為什麼必然呢?因為b[j]和a[i]配對之後的轉移是max(F[i-1][k])+1,而和之前的i配對則是max(F[i-1][k])+1。顯然有F[i][j]>F[i][j],i>i) 於是我們得出了狀態轉移方程:
a[i]!=b[j]: F[i][j]=F[i-1][j]
a[i]==b[j]: F[i][j]=max(F[i-1][k])+1 1<=k<=j-1&&b[j]>b[k]
不難看到,這是一個時間複雜度為O(n^3)的DP,離平方還有一段距離。
但是,這個演算法最關鍵的是,如果按照一個合理的遞推順序,max(F[i-1][k])的值我們可以在之前訪問F[i][k]的時候通過維護更新一個max變數得到。怎麼得到呢?首先遞推的順序必須是狀態的第一維在外層迴圈,第二維在內層迴圈。也就是算好了F[1][len(b)]再去算F[2][1]。 如果按照這個遞推順序我們可以在每次外層迴圈的開始加上令一個max變數為0,然後開始內層迴圈。當a[i]>b[j]的時候令max=F[i-1][j]。如果迴圈到了a[i]==b[j]的時候,則令F[i][j]=max+1。
最後答案是F[len(a)][1]..F[len(a)][len(b)]的最大值。 參考程式碼:

int f[1005][1005],a[1005],b[1005],i,j,t,n1,n2,max;
int main()
{
    scanf("%d",&t);
    while(t--)
    {
        scanf("%d%d",&n1,&n2);
        for(i=1; i<=n1; i++) 
            scanf("%d",&a[i]);
        for(i=1; i<=n2; i++) 
            scanf("%d",&b[i]);
        memset(f,0,sizeof(f));

//        a[i]!=b[j]:   F[i][j]=F[i-1][j]
//        a[i]==b[j]:   F[i][j]=max(F[i-1][k])+1(1<=k<=j-1&&b[j]>b[k])

        //如果按照一個合理的遞推順序,
        //max(F[i-1][k])的值我們可以在之前訪問F[i][k]的時候,
        //通過維護更新一個max變數得到。
        for(i=1; i<=n1; i++)
        {
            max=0;
            for(j=1; j<=n2; j++)
            {
                f[i][j]=f[i-1][j];           //可判斷也可不判斷
                if (a[i]>b[j]&&max<f[i-1][j])//max一直是f[i][j]下更新
                    max=f[i-1][j];
                if (a[i]==b[j])
                    f[i][j]=max+1;
            }
        }
        max=0;
        for(i=1; i<=n2; i++)
            if (max<f[n1][i]) 
                max=f[n1][i];
        printf("%d\n",max);
    }
}



其實還有一個很風騷的一維的演算法。在此基礎上壓掉了一維空間(時間還是平方)。i迴圈到x的時候,F[i]表示原來F[x][j]。之所以可以這樣,是因為如果a[i]!=b[j],因為F[x][j]=F[x-1][j]值不變,F[x]不用改變,沿用過去的就好了,和這個比較維護更新得到的max值依然是我們要的。而a[i]==b[j]的時候,就改變F[x]的值好了。具體結合程式碼理解。 參考程式碼:

int f[1005],a[1005],b[1005],i,j,t,n1,n2,max;
int main()
{
    scanf("%d",&t);
    while(t--)
    {
        scanf("%d%d",&n1,&n2);
        for(i=1; i<=n1; i++) scanf("%d",&a[i]);
        for(i=1; i<=n2; i++) scanf("%d",&b[i]);
        memset(f,0,sizeof(f));
        for(i=1; i<=n1; i++)
        {
            max=0;
            for(j=1; j<=n2; j++)
            {
                if (a[i]>b[j]&&max<f[j])
                    max=f[j];
                if (a[i]==b[j]) f[j]=max+1;
            }
        }
        max=0;
        for(i=1; i<=n2; i++) 
            if (max<f[i]) 
                max=f[i];
        printf("%d\n",max);
    }
}