1057 [NOIP2001]统计单词个数 划分区间 线性DP
链接:https://ac.nowcoder.com/acm/problem/16696
来源:牛客网
题目描述
给出一个长度不超过200的由小写英文字母组成的字母串(约定;该字串以每行20个字母的方式输入,且保证每行一定为20个)。要求将此字母串分成k份( 1 < k ≤ 40 ),且每份中包含的单词个数加起来总数最大(每份中包含的单词可以部分重叠。当选用一个单词之后,其第一个字母不能再用。例如字符串this中可包含this和is,选用this之后就不能包含th)。
单词在给出的一个不超过6个单词的字典中。
要求输出最大的个数。输入描述:
每组的第一行有2个正整数(p,k)
p表示字串的行数,k表示分为k个部分。
接下来的p行,每行均有20个字符。
再接下来有1个正整数s,表示字典中单词个数。(1 ≤ s ≤ 6 )
接下来的s行,每行均有1个单词。
输出描述:
1个整数,分别对应每组测试数据的相应结果。
分析
这题问的是给一场串字母分块,问每块所拥有的单词数之和最大是多少。
所以得先预处理出来每一块区间内的字符串数量是多少,
DP 考虑一个dp[i][j]表示前i个字母分成了j段时最大的单词数目,那么我们枚举一个位置p,
在这个位置将字符串分成两段,那么这个dp[i][j]=max(dp[p][j-1]+s[p+1][i]),
我们定义s[i][j]s[i][j]表示在文本串(即文章)的第i个位置到第j个位置,以每个位置开头,以j为结尾,总共有多少单词。
那么s数组可以用lenght(T) ^ 3×number(单词)的效率预处理,然后dp数组可以用length(T) ^ 2×k的效率直接搞出来。
DP的优化手段:如果单词数目比较多弄个Trie就可以把number的常数直接弄掉。同时s数组应该还可以用递推搞一搞,那么又可以把预处理的效率弄成length(T)
2
。然后在实际操作中dp数组还可以用单调队列优化一下,那么就可以把一个length(T)给弄掉。
PS:发现ms(f,0x3f)是负无穷
#include<bits/stdc++.h> using namespace std; const int N = 210; int val[N*N],ch[N*N][26],f[N][60],s[N][N]; int NodeCnt = 0,n,m,len,k;//字母个数,行数,单词个数,字母长度,分块数量。 string in,T; void insert(string in) { int u = 0; for(int i = 0;i<in.length();i++ ) { int c = in[i] - 'a'; if(!ch[u][c]) ch[u][c] = ++ NodeCnt; u = ch[u][c]; } val[u] = 1; } int query(int l,int r) { int sum = 0; for(int i = l;i<=r;i++) { int u = 0; for(int j = i;j<=r;j++) { int c = T[j] - 'a'; if(!ch[u][c]) break; u = ch[u][c]; if(val[u]) {sum ++ ;break;} } } return sum; } int main() { ios::sync_with_stdio(0); while(cin>>n>>k) { T = ""; for(int i = 0;i<n;i++ ) { cin>>in; T = T + in; } len = T.length(); cin>>m; for(int i = 0;i<m;i++ ){ cin>>in; insert(in); } for(int i = 0;i<len;i++ ){ for(int j = i;j<len;j++ ) { s[i][j] = query(i,j); } } memset(f,~0x3f,sizeof f); for(int i = 0;i<len;i++) { f[i][1] = s[0][i]; } for(int i = 0;i<len;i++) { for(int j = 2;j<=k;j++) { for(int p = 0;p<i;p++) { f[i][j] = max(f[i][j],f[p][j-1] + s[p+1][i]); } } } cout<<f[len - 1][k]; } return 0; }