牛客練習賽60 C-操作集錦[dp]

牛客練習賽60 C-操作集錦[dp]

題目鏈接:https://ac.nowcoder.com/acm/contest/4853/C

時間限制:C/C++ 1秒,其他語言2秒
空間限制:C/C++ 262144K,其他語言524288K
64bit IO Format: %lld

題目描述

有一款自走棋有26種操作,每種操作我們都用a,b,c,d,…,x,y,z的符號來代替.
現在牛牛有一個長度爲n的操作序列,他現在可以從裏面拿出某些操作來組合成一個操作視頻, 比如說操作序列是abcd,那麼操作視頻就有a,b,c,d,ab,ac,ad等(也就是操作序列的子序列).他現在想知道長度爲k且本質不同的操作視頻有多少種.
比如對於abab,長度爲2且本質不同的結果有ab,aa,ba,bb。
考慮到答案可能非常大,你只需要輸出在模1e9+7意義下的答案就可以了.

輸入描述

第一行兩個整數n,k.
第二行一個長度爲n的字符串,保證只存在小寫字母.

輸出描述

一行一個整數表示長度爲k且本質不同的操作視頻的個數.

樣例輸入

3 1
abc

樣例輸出

3

備註

1≤ n ≤1e3
0≤ k ≤n

分析

其實題目辣麼長,說白了也就是:找出給定序列中的不重複定長子序列個數
(題目是操作集錦,爲什麼正文就變成操作視頻了,盲猜機翻)
好吧,我承認有那麼一瞬(把子序列看成了字串),我覺得只要把所有定長字串放入vector,然後unique一下就完事了,敲到一半,驀然回首,突然發現是子序列,共有Ckn個,這複雜度不是開玩笑的,完全沒有機會把子序列哪怕掃一遍。

真·分析

在這種情況下,我能想到的只有dp了,用動態規劃來減少無必要的掃描,降低復時間雜度。
令dp[i]表示前i個字符中,定長子序列個數,pre[i]表示字符i上一次出現的下標,str儲存字符串。
兩種情況,構建狀態轉移方程:
①若pre[str[i]]==0,即前i-1個字符未出現過str[i]
dp[i]=dp[i-1]+m[k-1];
這裏的m[k-1]代表 前i-1個字符中長度爲k-1的子序列個數,這些序列接上str[i]組成新的長度爲k的子序列。
②若pre[str[i]]!=0,即前i-1個字符出現過str[i]
dp[i]=dp[i-1]+m[k-1]-g[k-1];
這裏的g[k-1]表示 前pre[str[i]]-1 (str[i]上一次出現位置的前一個位置)個字符中長度爲k-1的子序列的個數,這些子序列後接str[i] 和 後接 str[pre[str[i]]]構成的子序列相同,所以要去重。

那麼問題來了,這裏的m,g怎麼求呢?
其實仔細觀察便會發現,這裏的狀態轉移方程其實是遞歸定義的,m[i],g[i]其實本質與dp[i]相同,只不過k變成了k-1而已,那麼求k-1又需要k-2…,但我們最好不要真的寫成遞歸,容易重複計算以及爆棧,衆所周知,遞歸可以改寫成循環,那我們不妨從長度1開始,一步一步地向k迭代。

那麼,空間複雜度需要增加一維,dp[i][j]代表前 j 個字符中長度爲 i 的子序列個數。
完整的狀態轉移方程如下:
①若pre[str[i]]==0,即前i-1個字符未出現過str[i]
dp[i][j] = dp[i - 1][j - 1] + dp[i][j - 1];
②若pre[str[i]]!=0,即前i-1個字符出現過str[i]
dp[i][j] = dp[i - 1][j - 1] + dp[i][j - 1] - dp[i-1][pre[str[j]]-1];

需要注意的是取模問題,由於存在減法,可能出現負數,所以要先加上mod再取模
比如(12-9)%10==(12%10-9%10+10)%10

"Talk is Cheap. Show me the Code."

#include<stdio.h>
#include<stdlib.h>
#include<algorithm>
#include<math.h>
#include<string>
#include<vector>
#include<string.h>
using namespace std;
const int maxn = 1e3 + 5;
const int mod = 1e9+7;
typedef long long ll;
char str[maxn];
ll dp[maxn][maxn];
int pre[256];
int main(void)
{
    int n, k;
    scanf("%d %d", &n, &k);
    scanf("%s", str+1);
    for (int i = 1; i <= n; i++) {//預處理出長度爲1的情況
        if (pre[str[i]])
            dp[1][i] = dp[1][i - 1];
        else
            dp[1][i] = dp[1][i - 1] + 1;
        pre[str[i]] = i;
    }
    for (int i = 2; i <= k; i++) {
        memset(pre, 0, sizeof(pre));
        for (int j = 2; j <= n; j++) {
            if (pre[str[j]] == 0)
                dp[i][j] = dp[i - 1][j - 1] + dp[i][j - 1];
            else
                dp[i][j] = dp[i - 1][j - 1] + dp[i][j - 1] - dp[i-1][pre[str[j]]-1] + mod;
            dp[i][j] %= mod;
            pre[str[j]] = j;
        }
    }
    if (k)
        printf("%lld\n", dp[k][n]%mod);
    else
        printf("1\n");//特判k==0
    return 0;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章