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;
}
posted @ 2020-09-18 17:21  梦中霜雪梨花白  阅读(159)  评论(0编辑  收藏  举报