jyhlearning

导航

< 2025年3月 >
23 24 25 26 27 28 1
2 3 4 5 6 7 8
9 10 11 12 13 14 15
16 17 18 19 20 21 22
23 24 25 26 27 28 29
30 31 1 2 3 4 5

统计

[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% 的数据,2k401s6

【样例解释】
划分方案为 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   jyhlearning  阅读(118)  评论(0编辑  收藏  举报

相关博文:
阅读排行:
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· Docker 太简单,K8s 太复杂?w7panel 让容器管理更轻松!
点击右上角即可分享
微信分享提示