P1026 统计单词个数
题目描述
给出一个长度不超过 200 的由小写英文字母组成的字母串(约定;该字串以每行 20 个字母的方式输入,且保证每行一定为 20 个)。要求将此字母串分成 k 份( 1<k≤401<k \le 401<k≤40 ),且每份中包含的单词个数加起来总数最大(每份中包含的单词可以部分重叠。当选用一个单词之后,其第一个字母不能再用。例如字符串 this中可包含 this 和 is ,选用 this 之后就不能包含 th )。
单词在给出的一个不超过 6 个单词的字典中。
要求输出最大的个数。
输入输出格式
输入格式:每组的第一行有 2 个正整数( p,k )
p表示字串的行数, k 表示分为 k 个部分。
接下来的 p 行,每行均有 20 个字符。
再接下来有 1 个正整数 s ,表示字典中单词个数。( 1≤s≤61 \le s \le 61≤s≤6 )
接下来的 s 行,每行均有 1 个单词。
输出格式:1 个整数,分别对应每组测试数据的相应结果。
输入输出样例
1 3
thisisabookyouareaoh
4
is
a
ok
sab
7
说明
this/isabookyoua/reaoh
Solution:
字符串dp,套路就是定义状态$f[i][j]$表示到了母串$i$位置分成$j$块包含的单词最大个数。
对于本题,如果我们知道$s[i][j]$(表示母串$i$到$j$之间的单词个数),则很容易将其转化为区间dp,不难得到状态转移方程:$f[i][j]=max(f[i][j],f[p][j-1]+s[p+1][i]),\;p\in [1,i)$。
那么本题解决关键成了如何预处理$s[i][j]$,我的方法是字符串hash+暴力枚举,对母串和各匹配串均hash一下,然后直接枚举区间和断点,判断hash值是否相等就好了,然后注意细节:1、求hash值时可能爆int 2、每个位置只能做为一个匹配串的开头,匹配到了就直接break掉。
代码:
#include<bits/stdc++.h> #define il inline #define ll long long #define For(i,a,b) for(int (i)=(a);(i)<=(b);(i)++) #define Bor(i,a,b) for(int (i)=(b);(i)>=(a);(i)--) using namespace std; const int mod=998244353,P=131; int op[205],ha[205],l[7],p[7]; int n,m,k,len,f[205][45],g[205][205]; char s[205],t[205]; bool vis[205]; il void init(){ cin>>m>>k; while(m--) For(i,1,20) cin>>s[++len]; op[0]=1; For(i,1,len) op[i]=1ll*op[i-1]*P%mod,ha[i]=(1ll*ha[i-1]*P%mod+s[i]-'a')%mod; cin>>n; For(i,1,n) { cin>>t+1,l[i]=strlen(t+1); For(j,1,l[i]) p[i]=(1ll*p[i]*P+t[j]-'a')%mod; } For(i,1,len) For(j,i,len) For(k,i,j) For(o,1,n) if(k+l[o]-1<=j&&(ha[k+l[o]-1]-1ll*ha[k-1]*op[l[o]]%mod+mod)%mod==p[o]){g[i][j]++;break;} } int main(){ ios::sync_with_stdio(0); init(); For(o,1,k) For(i,1,len) For(j,o-1,i-1) f[i][o]=max(f[i][o],f[j][o-1]+g[j+1][i]); cout<<f[len][k]; return 0; }