jyhlearning

导航

[NOIP2001 提高组] 统计单词个数

[NOIP2001 提高组] 统计单词个数

题目描述

给出一个长度不超过 \(200\) 的由小写英文字母组成的字母串(该字串以每行 \(20\) 个字母的方式输入,且保证每行一定为 \(20\) 个)。要求将此字母串分成
\(k\) 份,且每份中包含的单词个数加起来总数最大。

每份中包含的单词可以部分重叠。当选用一个单词之后,其第一个字母不能再用。例如字符串 this 中可包含 this 和 is,选用 this 之后就不能包含
th。

单词在给出的一个不超过 \(6\) 个单词的字典中。

要求输出最大的个数。

输入格式

每组的第一行有两个正整数 \(p,k\)。
\(p\) 表示字串的行数,\(k\) 表示分为 \(k\) 个部分。

接下来的 \(p\) 行,每行均有 \(20\) 个字符。

再接下来有一个正整数 \(s\),表示字典中单词个数。
接下来的 \(s\) 行,每行均有一个单词。

输出格式

\(1\)个整数,分别对应每组测试数据的相应结果。

样例 #1

样例输入 #1

1 3
thisisabookyouareaoh
4
is
a
ok
sab

样例输出 #1

7

提示

【数据范围】
对于 \(100\%\) 的数据,\(2 \le k \le 40\),\(1 \le s \le 6\)。

【样例解释】
划分方案为 this / isabookyoua / reaoh

【题目来源】

NOIP 2001 提高组第三题

我的解答,第一次代码提交

#include <iostream>
#include <algorithm>
#include <string>
#include <vector>
using namespace std;
const int MAXC = 210, MAXK = 41;
int g[MAXC] = { 0 }, f[MAXC][MAXK] = { 0 };
int p, k, s;
string str;
vector<string> words;
bool cmp(string& e1, string& e2)
{
    return e1.size() < e2.size();
}
void get_num_str()//得到一在i长度下有多少个单词
{
    sort(words.begin(), words.end(), cmp);
    for (int i = 0; i < str.size(); i++)
    {
        for (string word : words)
        {
            if (str.substr(i, word.size()) == word)
            {
                g[i + word.size()] += 1;
                break;
            }
        }
    }
    for (int i = 1; i <= str.size(); i++)
    {
        g[i] += g[i - 1];
        cout<<g[i]<<endl;
    }
}
int main()
{
    cin >> p >> k;
    string temp;
    for (int i = 0; i < p; i++)
    {
        cin >> temp;
        str += temp;
    }
    cin >> s;
    for (int i = 0; i < s; i++)
    {
        cin >> temp;
        words.push_back(temp);
    }
    get_num_str();
    for (int i = 1; i <= str.size(); i++)//i长度下
    {
        for (int j = 0; j < k; j++)//k-1个划分成为k个部分
        {
            for (int x = 1; x < i; x++)//a表示在i段里一分为二的位置,在str[a]所属位置的后面进行划分
            {
                f[i][j] = max(f[x][j - 1] + g[i] - g[x + 1], f[i][j]);
            }
        }
    }
    cout << f[str.size()][k-1];
    return 0;
}

分析

这个代码能通过少部分的测试点(数据太水以至于测试点通过率能达到惊人的80%),其实这个里面有个致命的bug,就是g,g的定义就是在i长度下有多少个单词,如果想要求i-j区间的单词数目把他们相减就可以了,这种做法在特殊长度下就是失效了,比如说现在的字符串是“is”,而单词又是“is”和“s”,此时g[0]=0,g[1]=2,这时就会有歧义,g[1]-g[0]=2,但实际上现在想要表的是区间0-1的单词数目,实际上为“s”.所以这种做法必须要pass掉

如果抛弃这种投机取巧的做法,把g开成二维数组,岂不是就能消除这种歧义了?g[i][j]表示i-j区间内的所有单词数目。
得到改进代码

#include <iostream>
#include <string>
#include <vector>
#include <unordered_map>
using namespace std;
const int MAXC = 210, MAXK = 41;
int f[MAXC][MAXK] = { 0 },g[MAXC][MAXC]={0};
int p, k, s;
string str;
unordered_map<string, int> word_num;
vector<string> words;
bool check(int l, int r)
{
    string temp = str.substr(l, r - l + 1);
    for (string word : words)
    {
        if (temp.substr(0, word.size()) == word)
            return true;
    }
    return false;
}
void get_word_num()//这里做法很巧妙,从矩阵的右下角往左上角统计数目,避免了重复统计单词数目。
{
    for(int i=str.size()-1;i>=0;i--)
        for (int j = i; j >= 0; j--)
        {
            g[j][i] = g[j + 1][i];//g[j][i]=g[j+1][i]表示[j+1,i]区间包含[j,i]区间,因此是等于这个区间的,然后再加上多的一部分就是这个区间内的所有单词数。
            if (check(j, i))g[j][i]++;
        }
}
int main()
{
    cin >> p >> k;
    string temp;
    for (int i = 0; i < p; i++)
    {
        cin >> temp;
        str += temp;
    }
    cin >> s;
    for (int i = 0; i < s; i++)
    {
        cin >> temp;
        words.push_back(temp);
    }
    get_word_num();
    for (int i = 1; i <= str.size(); i++)//i长度下
    {
        for (int j = 1; j <= k; j++)//j个部分
        {
            for (int x = 0; x < i; x++)//x表示前半部分的长度
            {
                if(j-1<=x)
                    f[i][j] = max(f[x][j - 1] + g[x][i-1], f[i][j]);
            }
        }
    }
    cout << f[str.size()][k];
    return 0;
}

另一种写法,哈希+dp

#include <iostream>
#include <string>
#include <vector>
#include <unordered_map>
using namespace std;
const int MAXC = 210, MAXK = 41;
int f[MAXC][MAXK] = { 0 };
int p, k, s;
string str;
unordered_map<string, int> word_num;
vector<string> words;
int get_word_num(int l, int r)
{
    string temp = str.substr(l,r-l );
    int ans = 0;
    for (int i = 0; i < temp.size(); i++)
    {
        for (string word : words)
            if (temp.substr(i, word.size()) == word)
            {
                ans++;
                break;
            }
    }
    return ans;
}
void init()
{
    for (int i = 0; i < str.size(); i++)
        for (int j = i; j < str.size(); j++)
            word_num[str.substr(i, j - i + 1)] = get_word_num(i, j+1);
}
int main()
{
    cin >> p >> k;
    string temp;
    for (int i = 0; i < p; i++)
    {
        cin >> temp;
        str += temp;
    }
    cin >> s;
    for (int i = 0; i < s; i++)
    {
        cin >> temp;
        words.push_back(temp);
    }
    init();
    for (int i = 1; i <= str.size(); i++)//i长度下
    {
        for (int j = 1; j <= k; j++)//j个部分
        {
            for (int x = 0; x < i; x++)//x表示前半部分的长度
            {
                if(j-1<=x)
                    f[i][j] = max(f[x][j - 1] + word_num[str.substr(x,i-x)], f[i][j]);
            }
        }
    }
    cout << f[str.size()][k];
    return 0;
}

posted on 2022-09-22 20:53  jyhlearning  阅读(213)  评论(0)    收藏  举报