P1026 统计单词个数

P1026 统计单词个数

题目描述

给出一个长度不超过200的由小写英文字母组成的字母串(约定;该字串以每行20个字母的方式输入,且保证每行一定为20个)。要求将此字母串分成k份(1<k<=40),且每份中包含的单词个数加起来总数最大(每份中包含的单词可以部分重叠。当选用一个单词之后,其第一个字母不能再用。例如字符串this中可包含this和is,选用this之后就不能包含th)。

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

要求输出最大的个数。

输入输出格式

输入格式:

每组的第一行有二个正整数(p,k)

p表示字串的行数;

k表示分为k个部分。

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

再接下来有一个正整数s,表示字典中单词个数。(1<=s<=6)

接下来的s行,每行均有一个单词。

输出格式:

一个整数,分别对应每组测试数据的相应结果。

输入输出样例

输入样例#1:

1 3
thisisabookyouareaoh
4
is
a
ok
sab

输出样例#1:

7

说明

this/isabookyoua/reaoh

【问题分析】

刚看到这个题目觉得很迷茫,没入手点但是突然看到了闪亮的突破口:题目中说this包含this和is 但不包含th这也就是说在一个串内对于一个固定了起点的单词只能用一次,即使他还可以构成别的单词但他还是用一次。比如:串:thisa

字典:this is th

串中有this  is  th这三个单词,但是对于this 和 th 只用一次,也就是说枚举一下构成单词的起点,只要以该起点的串中包含可以构成一个以该起点开头的单词,那么就说明这个串中多包含一个单词。

这样可以得出下面的结果:

枚举的起点                                   结论:

t                                            至少包含1个

h                                           至少包含1个

i                                            至少包含2个

s                                            至少包含2个

a                                            至少包含2个

考虑到这里,就有点眉目了。

题目中要将串分k个部分也就是说从一个点截断后一个单词就未必可以构成了。比如上例要分3个部分,合理的其中的一个部分至多有3个字母,这样this这个单词就构不成了。

要是分5个部分,那就连一个单词都构不成了。

这样就需要对上面做个改动,上面的只控制了起点,而在题目中还需要限制终点,分完几个部分后,每部分终点不同可以构成的单词就不同了。

这样就需要再枚举终点了。

设计一个二维数组sum[i,j]统计从i到j的串中包含的单词的个数

状态转移方程:

sum[i+1,j]+1            (s[i,j]中包含以s[i]开头的单词)

sum[i,j]= sum[i+1,j]               (与上面相反)

注:(1)这里枚举字符的起点的顺序是从尾到头的。

(2)有人把上面这次也看做是一次动态规划,但我觉得更准确的说是递推。

求出所有的sum还差一步,就是不同的划分方法显然结果是不一样的,但是对于求解的问题我们可以这样把原问题分解成子问题:求把一个串分成k部分的最多单词个数可以看做是先把串的最后一部分分出来,再把前面一部分分解成k-1个部分,显然决策就是找到一种划分的方法是前面的k-1部分的单词+最后一部分的单词最多。

显然这个问题满足最优化原理,那满不满足无后效性呢?

对于一个串分解出最后一部分在分解前面的那部分是根本就不会涉及分好的这部分,换句话说每次分解都会把串分解的更小,对于分解这个更小的串不会用到不属于这个小串的元素。这就满足无后效性。

具体求解过程:

设计一个状态opt[i,j]表示把从1到j的串分成i份可以得到最多的单词的个数。决策就是枚举分割点使当前这种分割方法可以获得最多的单词。

状态转移方程:opt[i,j]=max(opt[i-1,t]+sum[t+1,j])       (i<t<j)

边界条件:opt[1,i]=sum[1,i]                         (0<i<=L)

时间复杂度:状态数O(N2)*决策数O(N)=O(N3),空间复杂度:O(N2)。

sum[i][j]表示第i个字母到第j个字母一共可以形成多少个单词。

sum[i][j]=sum[i][-1]+(包含可以添加最后一个字母j的单词的总个数)。

注意到题目中有一个非常特殊的地方,就是以串中某个位置的字母为首字母,最多只能分出一个单词。由于在拆分字符串的过程中,如果以某位置为首某个较短单词被截断,那么以该位置为首的较长单词必然也会被截断。也就是说,对于各个位置来说我们选取较短的单词总不会比选取较长的单词所形成的单词少。这样我们可以定义一个在位置的参数表示以位置的字母为首字母,所能形成的最短单词的长度。这样如果在这个位置加上这个单词的长度之内截断,则以该位置为首字母就不能形成单词,否则就可能形成一个单词。这样对于所有的不同个首位置,我们只要在各个位置依次对各个单词进行匹配就以得出所对应的的值,这一部分的复杂度为O(wl2)。然后是计算把字串分为多个部分的过程中有什么单词可以留下。

  1 #include <bits/stdc++.h>
  2 using namespace std;
  3
  4 const int maxn=250;
  5 const int maxm=10;
  6
  7 string s;
  8 int slen;
  9 string words[maxm];
 10 int wordslen[maxm];
 11 int f[maxn][maxm];
 12 int sum[maxn][maxn];
 13 int n,k,m;
 14
 15 //读入数据
 16 void init()
 17 {
 18     string tmp;
 19     cin>>n>>k;
 20     for (int i=1;i<=n;i++)
 21     {
 22         cin>>tmp;
 23         s+=tmp;
 24     }
 25     slen=s.size();
 26     //读入字典
 27     cin>>m;
 28     for (int i=1;i<=m;i++)
 29     {
 30         cin>>words[i];
 31         wordslen[i]=words[i].size();
 32     }
 33 }
 34
 35 //l头r尾
 36 int add(int l,int r)
 37 {
 38     int ans=0;
 39     //前面还有字符
 40     if (r-1>=0) ans=sum[l][r-1];
 41     bool vis[maxn]={0};
 42     for (int i=1;i<=m;i++)
 43     {
 44         int qd=r-wordslen[i]+1;
 45         if (qd<l) continue;
 46         if (qd==s.find(words[i],qd))
 47         {
 48             if (vis[qd]) continue;
 49             vis[qd]=1;
 50             ans++;
 51             for (int j=1;j<=m;j++)
 52             {
 53                 int dq=r-wordslen[j];
 54                 if (dq==qd)
 55                     if (dq==s.find(words[j],dq))
 56                     {
 57                         ans--;
 58                         break;
 59                     }
 60             }
 61         }
 62     }
 63     return ans;
 64 }
 65
 66 //得到sum数组
 67 void gsum()
 68 {
 69     for (int i=0;i<=slen-1;i++)//串头
 70         for (int j=i;j<=slen-1;j++)//串尾
 71         {
 72             sum[i][j]=add(i,j);
 73           //  printf("%d %d --> %d\n",i,j,sum[i][j]);
 74         }
 75 }
 76
 77 void work()
 78 {
 79     for (int i=0;i<=slen-2;i++)
 80         f[i][1]=sum[0][i];
 81     for (int i=0;i<=slen-2;i++)
 82         for (int j=2;j<=min(k-1,i+1);j++)
 83             for (int u=j-2;u<=i-1;u++)
 84                 f[i][j]=max(f[i][j],f[u][j-1]+sum[u+1][i]);
 85     int ans=0;
 86     if (k==0)
 87         ans=sum[0][slen-1];
 88     else
 89         for (int i=k-1;i<=slen-2;i++)
 90             ans=max(ans,f[i][k-1]+sum[i+1][slen-1]);
 91     printf("%d\n",ans);
 92 }
 93
 94 int main()
 95 {
 96     init();
 97     gsum();
 98     work();
 99     return 0;
100 }
时间: 2024-10-27 01:35:24

P1026 统计单词个数的相关文章

luogu P1026 统计单词个数

题目链接 luogu P1026 统计单词个数 题解 贪心的预处理母本串从i到j的最大单词数 然后dp[i][j] 表示从前i个切了k次最优解 转移显然 代码 #include<cstdio> #include<algorithm> #include<cstring> const int maxn = 507; char a[maxn]; char s[maxn],t[maxn]; int dp[maxn][55]; int n,k,q; int num[maxn][m

P1026 统计单词个数 [dp]

P1026 统计单词个数 这道题看上去就是要用dp的样子.裸裸的dp题无误. 首先要把分开的字符串合成那个长度小于等于\(200\)的总字符串. 然后做个预处理,预处理出任意区间内的单词个数,设为\(sum[i][j]\). 有一个神奇的地方: 当选用一个单词之后,其第一个字母不能再用. 题解里面有这么一种解决方式: 倒序枚举\(j\)和\(i\).初始化\(sum[i][j] = sum[i + 1][j]\).如果子串中从一开始就存在单词,加1. 其实不怎么知道原理这种做法还刚好满足了上面的

P1026 统计单词个数——substr

P1026 统计单词个数 string 基本操作: substr(x,y) x是起始位置,y是长度: 返回的是这一段字符串: 先预处理sum[i][j],表示以i开头,最多的单词数: 从后往前寻找,保证开头没有被用过: sum[i][j]=sum[i+1][j]; 再找是否有新单词出现: s.find()==0说明找到单词以开头开始: 然后dp,f[i][j]表示以i结尾分j段的最大单词数: #include<cstdio> #include<string> #include<

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

题目描述 给出一个长度不超过200的由小写英文字母组成的字母串(约定;该字串以每行20个字母的方式输入,且保 证每行一定为20个).要求将此字母串分成k份(1<k<=40),且每份中包含的单词个数加起来总数最大(每份中包含的单词可以部分重叠. 当选用一个单词之后,其第一个字母不能再用.例如字符串this中可包含this和is,选用this之后就不能包含th). 单词在给出的一个不超过6个单词的字典中. 要求输出最大的个数. 输入输出格式 输入格式: 每组的第一行有二个正整数(p,k) p表示字

洛谷 P1026 统计单词个数 区间DP

题目描述 给出一个长度不超过200的由小写英文字母组成的字母串(约定;该字串以每行20个字母的方式输入,且保证每行一定为20个).要求将此字母串分成k份(1<k<=40),且每份中包含的单词个数加起来总数最大(每份中包含的单词可以部分重叠.当选用一个单词之后,其第一个字母不能再用.例如字符串this中可包含this和is,选用this之后就不能包含th). 单词在给出的一个不超过6个单词的字典中. 要求输出最大的个数. 输入输出格式 输入格式: 每组的第一行有二个正整数(p,k) p表示字串的

P1026 统计单词个数 【dp】

题目描述 给出一个长度不超过 200200 的由小写英文字母组成的字母串(该字串以每行 2020 个字母的方式输入,且保证每行一定为 2020 个).要求将此字母串分成 kk 份,且每份中包含的单词个数加起来总数最大. 每份中包含的单词可以部分重叠.当选用一个单词之后,其第一个字母不能再用.例如字符串 this 中可包含 this 和 is,选用 this 之后就不能包含 th. 单词在给出的一个不超过 66 个单词的字典中. 要求输出最大的个数. 输入格式 每组的第一行有两个正整数 p,kp,

luogu P1026 统计单词个数 序列DP

dp[i][k]表示,在i及i左侧,分成k块,的最大单词数目. w[i][j]表示,在[i,j],内部多少个单词在此区间开始和结束. 转移方程为dp[i][k] = max(dp[j][k - 1] + w[j + 1][i]) 通过考虑每个单词的结束而非开始,而避开后后效性问题. 如果某个单词是另一个单词的前缀,则另一个单词无需考虑. 字符串处理的有问题,调了挺久的.... 1 #include <cstdio> 2 #include <cstring> 3 #include &

洛谷 【P1026】统计单词个数

P1026 统计单词个数 题目描述 给出一个长度不超过200的由小写英文字母组成的字母串(约定;该字串以每行20个字母的方式输入,且保证每行一定为20个).要求将此字母串分成k份(1<k<=40),且每份中包含的单词个数加起来总数最大(每份中包含的单词可以部分重叠.当选用一个单词之后,其第一个字母不能再用.例如字符串this中可包含this和is,选用this之后就不能包含th). 单词在给出的一个不超过6个单词的字典中. 要求输出最大的个数. 输入输出格式 输入格式: 每组的第一行有二个正整

统计单词个数

驱动开发的第六章让我了解了统计单词个数的Linux驱动程序开发和测试的完整过程. Linux系统将每一个驱动都映射成一个文件,这些文件被称为设备文件或驱动文件,都保存在/dev目录中.由于大多数Linux驱动都有与其对应的设备文件,因此与Linux驱动交换数据就变成了与设备文件交换数据. 编写Linux驱动程序的步骤:第一步,建立Linux驱动骨架(装载和卸载Linux驱动):第二步,注册和注销设备文件:第三步,指定与驱动相关的信息:第四步,指定回调函数:第五步,编写业务逻辑:第六步,编写mak