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


原题链接

P1039
题目类型: 普 及 + / 提 高 {\color{yellow} 普及+/提高} +/
AC记录:Accepted

题目大意

给出一个字符串和一个字典,要求你把这个字符串分成 k k k份,使每一份里面包含的字典里的单词数总和最多。注意:单词之间可以重叠,但开头不能使用同一个位置,即每一个位置只能有一个单词以他为开头。如this中如果选了this这个单词,则可以再选is这个单词,但不能再选th

输入格式

第一行有二个正整数 p , k p,k p,k p p p表示字串的行数, k k k表示分为 k k k个部分。
接下来的 p p p行,每行均有 20 20 20个字符。代表字符串的第 ( p − 1 ) × 20 + 1 (p-1)\times 20+1 (p1)×20+1个字符到第 p × 20 p\times 20 p×20 个字符。
再接下来有一个正整数 s s s,表示字典中单词个数。
接下来的 s s s行,每行均有一个单词。

输出格式

输出唯一的一个整数,为最长的演讲总时间。

S a m p l e \mathbf{Sample} Sample I n p u t \mathbf{Input} Input

1 3
thisisabookyouareaoh
4
is
a
ok
sab

S a m p l e \mathbf{Sample} Sample O u t p u t \mathbf{Output} Output

7

H i n t & E x p l a i n \mathbf{Hint\&Explain} Hint&Explain
字符串分成this / isabookyoua / reaoh
第一段有一个is
第二段从左到右分别为is,sab,a,ok,a
第三段有一个a
总共7个单词。

数据范围

对于 100 % 100\% 100%的数据, 1 ≤ s ≤ 6 , 1 ≤ p ≤ 10 , 1 < k ≤ 40 1≤s≤6,1\le p\le 10,1<k\le 40 1s6,1p10,1<k40

解题思路

d p dp dp,就是 d p dp dp
按本题的要求,我们首先要实现一个 w o r k work work函数,代表在目标的字符串中有多少个单词。我在这里用的是直接用二维数组推一遍,当做初始化提前把 w o r k work work函数执行了。
作者这里使用的是倒推
我们想,每次插入一个字母,如果在当前位置上不能构成字母,那和不加这个字母有什么区别呢?如果在当前位置上能构成字母,也是在不插入这个字母的基础上单词量 + 1 +1 +1,于是,我们可以先设 w o r k i , j work_{i,j} worki,j为第 i i i位到第 j j j位所包含的单词数,初始时把他赋值为 w o r k i + 1 , j work_{i+1,j} worki+1,j,再判断他可不可以构成单词,如果可以, w o r k i , j work_{i,j} worki,j + 1 +1 +1
核心代码:

    for(int j=s.size(); j>=1; j--)
        for(int i=j; i>=1; i--)
        {
            word_num[i][j]=word_num[i+1][j];
            string temp=s.substr(i,j-i+1);
            for(int k=1; k<=num; k++)
            {
                if(temp.find(word[k])==0)
                {
                    word_num[i][j]++;
                    break;
                }
            }
            // cout<<"word_num["<<i<<"]["<<j<<"] = "<<word_num[i][j]<<endl;
        }

d p dp dp部分:
P1018 [NOIP2000 提高组] 乘积最大很像,可以设 f i , j f_{i,j} fi,j为前 i i i个字符分成 j j j段可以得到的最大单词数,再依次枚举 j j j段的起点进行状态转移,就可以了。
状态转移方程:
f i , j = { w o r k 1 , i j = 1 f i − 1 , j − 1 + w o r k i , j i = j max ⁡ j ≤ k ≤ i { f k − 1 , j − 1 + w o r k k , i } 1 ≤ i ≤ p × 20 , 1 ≤ j ≤ min ⁡ ( k , i ) f_{i,j}=\begin{cases} work_{1,i} & j=1 \\ f_{i-1,j-1}+work_{i,j} & i=j \\ \max_{j\le k\le i}\{f_{k-1,j-1}+work_{k,i}\} & 1\le i\le p\times 20,1\le j\le\min(k,i) \end{cases} fi,j=work1,ifi1,j1+worki,jmaxjki{fk1,j1+workk,i}j=1i=j1ip×20,1jmin(k,i)
而最后的答案为 f p × 20 , k f_{p\times 20,k} fp×20,k
第一条转移方程是当只分 1 1 1份的时候,那肯定全选,所以字母量为 w o r k 1 , i work_{1,i} work1,i
第二条转移方程是当份数和字母数相同,即每一份只有一个字母,所以直接从上一个状态 f i − 1 , j − 1 f_{i-1,j-1} fi1,j1加上当前的价值 w o r k i , j work_{i,j} worki,j就可以了。
第三条转移方程是枚举开头 k k k,其他的也不用多说了。

注意事项:

1.由于 w o r k work work函数使用的是倒推,所以循环要从大到小
2.一个字母只能对应一个单词的开头,所以一旦找到单词符合,直接break
3.在第三条转移方程中, j j j循环的上限为 min ⁡ ( k , i ) \min(k,i) min(k,i),是因为执行的过程中可能份数过多,字母不够分。


最后,祝大家早日
请添加图片描述

上代码

#include<iostream>
#include<cstring>

using namespace std;

string       word[16];
int          num;
int          word_num[210][210];
int          f[210][50];
int          n,m;

void work()
{
    cin>>n>>m;
    memset(f,0,sizeof(f));
    string s=" ";
    for(int i=1; i<=n; i++)
    {
        string temp;
        cin>>temp;
        s+=temp;
    }
    cin>>num;
    for(int i=1; i<=num; i++)
        cin>>word[i];
    for(int j=s.size(); j>=1; j--)
        for(int i=j; i>=1; i--)
        {
            word_num[i][j]=word_num[i+1][j];
            string temp=s.substr(i,j-i+1);
            for(int k=1; k<=num; k++)
            {
                if(temp.find(word[k])==0)
                {
                    word_num[i][j]++;
                    break;
                }
            }
            // cout<<"word_num["<<i<<"]["<<j<<"] = "<<word_num[i][j]<<endl;
        }
    // for(int i=1; i<=n*20; i++)
    //     for(int j=i; j<=n*20; j++)
    //         cout<<"word_num["<<i<<"]["<<j<<"] = "<<word_num[i][j]<<endl;
    for(int i=1; i<=m; i++)
        f[i][i]=f[i-1][i-1]+word_num[i][i];
    for(int i=1; i<s.size(); i++)
        f[i][1]=word_num[1][i];
    for(int i=1; i<s.size(); i++)
        for(int j=1; j<=m&&j<i; j++)
            for(int k=j; k<=i; k++)
                f[i][j]=max(f[i][j],f[k-1][j-1]+word_num[k][i]);
    cout<<f[s.size()-1][m]<<endl;
    // for(int i=1; i<=n*20; i++)
    //     for(int j=0; j<=m; j++)
    //         cout<<"f["<<i<<"]["<<j<<"] = "<<f[i][j]<<endl;
    return;
}

int main()
{
    ios::sync_with_stdio(false);
    cin.tie(0);
    // int t;
    // cin>>t;
    // while(t--)
        work();
    return 0;
}

完美切题 ∼ \sim

  • 3
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: 题目描述: 给定一个英文文章,统计其中不同单词个数。 输入格式: 输入文件包含若干行,每行是一个由大小写字母、逗号、句号和空格成的字符串,表示一篇英文文章。每个字符串的长度不超过100。 输出格式: 输出文件仅一行,包含一个整数,表示输入文件中不同单词个数。不区分大小写,相同的单词计为一个。 输入样例: This is a test. Hello world, hello China. 输出样例: 7 解题思路: 本题需要统计文章中不同单词个数,不区分大小写,相同的单词计为一个。因此,我们可以使用哈希表来统计每个单词出现的次数,最后输出哈希表中不同单词个数即可。 具体实现时,我们可以先将文章中的所有单词转换为小写字母,然后使用字符串流istringstream将每个单词读入,再将其插入哈希表中。最后,输出哈希表中不同单词个数即可。 代码实现: ### 回答2: 题目描述 给定一篇英语文章,统计其中单词的数量。单词是指仅由英文字母成的、不包含数字、标点符号等非字母字符的字符串。大小写不敏感,例如“abc”和“ABC”视为同一个单词。 输入格式: 共一行,为英语文章,长度不超过1000。 输出格式: 共一行,为不同单词的数量。 思路分析 本题可以采用 map 或 set 来记录每个单词出现的次数,也可以通过字符串处理直接进行字符判断,统计单词数。 方法一 首先将文章中所有小写字母转换成大写字母,再遍历每个字符,若是字母,则将其加入当前单词中,若是除字母外的其他字符,则将当前单词加入 set,清空当前单词,同时统计单词总数。 代码如下: #include <iostream> #include <set> using namespace std; int main() { string s; getline(cin, s); int n = s.size(); set<string> words; string cur_word; for(int i = 0; i < n; i ++) { if(s[i] >= 'a' && s[i] <= 'z') s[i] = s[i] - 'a' + 'A'; if(s[i] >= 'A' && s[i] <= 'Z') cur_word += s[i]; else { if(cur_word.size()) words.insert(cur_word); cur_word.clear(); } } if(cur_word.size()) words.insert(cur_word); cout << words.size() << endl; return 0; } 方法二 使用 STL 的 map 容器,记录每个单词出现的次数,统计不同单词的数量。 代码如下: #include <iostream> #include <map> using namespace std; int main() { map<string, int> words; string s, cur_word; getline(cin, s); int n = s.size(); for (int i = 0; i < n; i ++) { if(s[i] >= 'a' && s[i] <= 'z') s[i] = s[i] - 'a' + 'A'; if(s[i] >= 'A' && s[i] <= 'Z') cur_word += s[i]; else { if(cur_word.size()) words[cur_word] ++; cur_word.clear(); } } if(cur_word.size()) words[cur_word] ++; cout << words.size() << endl; return 0; } C++ 代码 ### 回答3: 题目描述: 给定一个以空格为分隔符的字符串,统计其中单词的数目。 分析: 本题其实是一个字符串的基础操作,需要用到的知识点包括字符串的定义和基本操作、循环语句等。 对于本题,我们可以使用一个计数器来记录字符串中单词的数目,每次遇到空格符就将计数器加一。由于题目中要求单词的前后不能有空格,所以我们需要对字符串首位的空格进行处理。最后输出计数器的值即可。 代码实现: String s = sc.nextLine();//读取输入的字符串 s = s.trim();//去掉字符串首尾的空格 int count = 0;//定义计数器 for(int i = 0; i < s.length(); i++){//循环遍历字符串中的每一个字符 if(s.charAt(i) == ' '){//遇到空格符,计数器加一 count++; } } System.out.println(count+1);//输出计数器的值 注:这里加一的原因是最后一个单词后面没有空格,所以需要再加一。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值