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
。假設匹配串只有 is
, this
和 th
。那麼 \(v(1,3) = 1\) (is
), \(v(0,3) = 2\) (this
,is
)。我們可以發現,由於
this
以t
(也就是 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\) 在內層。
這裡可以使用 string
的 substr(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;
}