P1026 统计单词个数

题目描述

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

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

要求输出最大的个数。

输入输出格式

输入格式:

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

p表示字串的行数, k 表示分为 k 个部分。

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

再接下来有 1 个正整数 s ,表示字典中单词个数。( 1≤s≤61 le s le 61s6 )

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

输出格式:

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

输入输出样例

输入样例#1: 
1 3
thisisabookyouareaoh
4
is
a
ok
sab
输出样例#1: 
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]),;pin [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;
}
原文地址:https://www.cnblogs.com/five20/p/9427021.html