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 (p−1)×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,1
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,ifi−1,j−1+worki,jmaxj≤k≤i{fk−1,j−1+workk,i}j=1i=j1≤i≤p×20,1≤j≤min(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} fi−1,j−1加上当前的价值 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 ∼