jyhlearning

导航

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

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

题目描述

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

每份中包含的单词可以部分重叠。当选用一个单词之后,其第一个字母不能再用。例如字符串 this 中可包含 thisis,选用 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  阅读(108)  评论(0编辑  收藏  举报