1. 程式人生 > 實用技巧 >P1026 統計單詞個數 題解

P1026 統計單詞個數 題解

P1026 統計單詞個數 題解

一道比較好的線性dp題目。
dp中一類比較典型的模型就是將資料分成一定的段落,使某一特定的值最大。
\(f(i,j)\) 表示將前 \(i\) 個數據分成 \(j\) 段,所能獲得的最大目標值。則有

\[f(i,j) = \max_{i<=k<j}f(k,j-1)+v(k+1,i) \]

其中v(l,r)是表示在段落 \([l,r]\) 上的一個值,具體意義根據題目而定。
初始狀態:

\[f(i,1) = v(1,i) \]

目標狀態:

\[f(N,K) \]

其中 \(N\) 表示資料總量,\(K\) 表示要求分成的段落數。
此演算法的時間複雜度為 \(O(n^3)\)

我們再來看這道題。很明顯,我們可以用 \(v(l,r)\) 表示在字串 \([l,r]\)中間的單詞數量,然後上面的dp方程就是正解。這裡注意,我用的是string,下標是以 \(0\) 開始的。
那麼怎麼求 \(v(l,r)\) 呢?我們仔細觀察題目:

每份中包含的單詞可以部分重疊。當選用一個單詞之後,其第一個字母不能再用。例如字串 this 中可包含 this 和 is,選用 this 之後就不能包含 th。

我們可以再用一個dp。我們嘗試用 \(v(l,r)\) 的值推出另外的 \(v\) 值。比如說上例,我們可以發現 this 字串的 \([1,3]\) 部分是 his\([0,3]\)

this。假設匹配串只有 isthisth 。那麼 \(v(1,3) = 1\) (is), \(v(0,3) = 2\) (this,is)。
我們可以發現,由於 thist(也就是 str[0])開頭,並且包含在 \([0,3]\) 之中,所以 \(v(0,3)=v(1,3)+1\)
所以得出通式:

\[v(l,r) = \begin{cases} v(l+1,r),沒有以 str[l] 開頭的,且能在 str[l,r] 中找到的匹配串。\\ v(l+1,r) +1,有以 str[l] 開頭的,且能在 str[l,r] 中找到的匹配串。 \end{cases} \]

同時,

當選用一個單詞之後,其第一個字母不能再用。

所以即使有多個滿足上述條件的匹配串,也只會 \(+1\)
這裡需要注意dp的順序,是從大到小列舉\(l,r\),其中 \(r\) 在外層, \(l\) 在內層。
這裡可以使用 stringsubstr(l,k) 函式,它表示擷取以 \(l\) 開頭,長度為 \(k\) 的子串。還有 find(str) 函式,它表示從源字串裡搜尋子字串 \(str\) ,若搜到了則返回 0,否則返回 1。

Code:

#include <bits/stdc++.h>
using namespace std;
constexpr int MAXN = 205;

string str, sub[MAXN];
int p, k, s;
int f[MAXN][MAXN];
int value[MAXN][MAXN];

void init()
{
    cin >> p >> k;
    for (int i = 1; i <= p; i++)
    {
        string tmp;
        cin >> tmp;
        str += tmp;
    }
    cin >> s;
    for (int i = 1; i <= s; i++)
    {
        cin >> sub[i];
    }
}
bool isthere(int l, int r)
{
    string obj = str.substr(l, r - l + 1);
    for (int i = 1; i <= s; i++)
    {
        if (!obj.find(sub[i]))
            return true;
    }
    return false;
}
void get_value()
{
    for (int j = str.length() - 1; j >= 0; j--)
    {
        for (int i = j - 1; i >= 0; i--)
        {
            value[i][j] = value[i + 1][j];
            if (isthere(i, j))
                value[i][j]++;
        }
    }
}
void dp()
{
    for (unsigned i = 0; i < str.length(); i++)
    {
        f[i][1] = value[0][i];
    }
    for (unsigned i = 0; i < str.length(); i++)
    {
        for (unsigned j = 2; j <= k; j++)
        {
            for (unsigned br = j; br < i; br++)
            {
                f[i][j] = max(f[i][j], f[br][j - 1] + value[br + 1][i]);
            }
        }
    }
    cout << f[str.length() - 1][k] << endl;
}
int main()
{
    init();
    get_value();
    dp();  
    return 0;
}