[NOIP2015 提高組] 子串

Yorg發表於2024-10-03

演算法

狀態定義

最初顯然可以想到
\(f[i][j][k]\) 表示 \(A\) 串前 \(i\) 個, \(B\) 串前 \(j\) 個, 分割了 \(k\) 個子串
但是這樣無法遞推 \(k\)

於是加上一位 \(f[i][j][k][0/1]\), 最後一維表示是否選擇 \(A\) 子串當前這一位, 也就可以遞推的計算

狀態轉移

  • 當前位置不使用
    \(f[i][j][k][0]=f[i-1][j][k][0]+f[i-1][j][k][1]\), 顯然

  • 當前位置使用
    \(f[i][j][k][1]=f[i-1][j-1][k-1][0]+f[i-1][j-1][k][1]+f[i-1][j-1][k-1][1] \leftarrow (a[i]==b[j])\)

邊界條件

  • 對於A子串前i位,匹配B串第1個字元,那麼只可能使用1個字串,這種情況如果第i位不進行匹配,那麼方案數就是之前所有能與第1位匹配的字元,所以 \(f[i][1][1][0]=sum(a[1~i-1]==b[1])\)

  • 同上,但如果B串第一位和A串某一位匹配了話,那麼 \(f[i][1][1][1]=1\) 是顯然的

答案

即為 \(f[n][m][k][0/1]\)

空間複雜度最佳化

一眼滾動陣列

  for (int i=1;i<=n;i++){
        swap(now,pre); //交換,只要i改變
        f[now][1][1][0]=s;//邊界1
        if (a[i]==b[1]) f[now][1][1][1]=1,s++;//邊界2,解決了s統計的問題
        for (int j=2;j<=m;j++){
            for (int k=1;k<=t;k++){
                if (a[i]==b[j]){
                    f[now][j][k][1]=((f[pre][j-1][k-1][1]+f[pre][j-1][k][1])%prime+f[pre][j-1][k-1][0])%prime;
                }//方程2
                f[now][j][k][0]=(f[pre][j][k][0]+f[pre][j][k][1])%prime;//方程1
            }
        }
     for(int j=1;j<=m;j++)
          for(int k=1;k<=t;k++)
            f[pre][j][k][1]=f[pre][j][k][0]=0;//清空
    }

總結

狀態的設計可以從需要遞推的項中找靈感
注意初始值一般為一行或一個點

相關文章