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,11s6,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["<
        }

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
#include

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["<
        }
    // for(int i=1; i<=n*20; i++)
    //     for(int j=i; j<=n*20; j++)
    //         cout<<"word_num["<
    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["<
    return;
}

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

完美切题 ∼ \sim

你可能感兴趣的:(洛谷题库题目,动态规划,算法,c++)